Эх сурвалжийг харах

为了防止 公用组件 不同页面有不同的逻辑,所以各自复制了一份组建,在进行调整。

会飞的电脑 2 жил өмнө
parent
commit
2489b6bd8d

+ 1 - 2
package.json

@@ -3,8 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "scripts": {
-    "serve": "npm run serve",
-    "install": "npm install",
+    "serve": "vue-cli-service serve",
     "build": "vue-cli-service build"
   },
   "dependencies": {

+ 41 - 11
src/App.vue

@@ -9,9 +9,9 @@
       </div>
 
       <v-tabs v-if="calcPwdMD5() === pwdMD5" v-model="tab">
-        <v-tab key="history">History</v-tab>
-        <v-tab key="pending">Pending</v-tab>
-        <v-tab v-if="ifShowHl" key="houliang">houliang</v-tab>
+        <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-tabs>
 
       <v-spacer></v-spacer>
@@ -49,7 +49,8 @@
             <v-col cols="6">
               <v-select persistent-hint return-object single-line
                         item-text="networkName" item-value="id" label="请选择Chain"
-                        :items="chainList" :hint="`${chain.id}   ${chain.networkName} ${chain.explorer}`" v-model="chain"></v-select>
+                        :items="chainList" :hint="`${chain.id}   ${chain.networkName} ${chain.explorer}`"
+                        v-model="chain"></v-select>
             </v-col>
           </v-row>
           <v-row align="center" v-if="chain.id !== ''">
@@ -62,13 +63,13 @@
       </v-tabs-items>
       <v-tabs-items v-else v-model="tab">
         <v-tab-item key="history">
-          <History :chain="chain"></History>
+          <History :chain="chain" ref="history"></History>
         </v-tab-item>
         <v-tab-item key="pending">
-          <Pending :chain="chain"></Pending>
+          <Pending :chain="chain" ref="pending"></Pending>
         </v-tab-item>
-        <v-tab-item v-if="ifShowHl" key="houliang">
-          <houliang :chain="chain"></houliang>
+        <v-tab-item key="hddress">
+          <Hddress :chain="chain" ref="hddress"></Hddress>
         </v-tab-item>
       </v-tabs-items>
     </v-main>
@@ -80,6 +81,8 @@
 import History from '@/components/History'
 import Pending from '@/components/Pending'
 import houliang from '@/components/hl/houliang'
+import Hddress from '@/components/Hddress'
+
 import HttpKit from "@/plugins/kit/HttpKit";
 import Chain from '@/plugins/model/Chain.js'
 import md5 from 'md5-node'
@@ -90,6 +93,7 @@ export default {
     History,
     Pending,
     houliang,
+    Hddress,
   },
   data: () => ({
     tab: 'history',
@@ -102,7 +106,7 @@ export default {
     chainList: [],
     pwdMD5: '7f5029bbd4daae18f3ae4cd910f0f4f0',
     pwd: process.env.NODE_ENV === 'development' ? 'qwe410410' : '',
-    ifShowHl: process.env.NODE_ENV === 'development' ? true : false
+    ifShowHl: process.env.NODE_ENV === 'development' ? false : false
   }),
   methods: {
     calcPwdMD5() {
@@ -114,10 +118,36 @@ export default {
     selectConfirm() {
       this.selected = true
       HttpKit.BASE_URL = this.chain.explorer
-    }
+    },
+    /**
+     * 切换tab 触发
+     * 提示:因为History.vue 于 Pending.vue 公用组件
+     * 所以内部 jq 渲染临时组件,在切换tab 需要做好手动控制
+     * */
+    historyChange() {
+      if (this.calcPwdMD5() !== this.pwdMD5 || this.$refs.history === undefined) {
+        return
+      }
+
+      this.$refs.history.prePackQuery()
+      this.$refs.history.afterPackQuery()
+    },
+    pendingChange() {
+      if (this.calcPwdMD5() !== this.pwdMD5 || this.$refs.pending === undefined) {
+        return
+      }
+      this.$refs.pending.prePackQuery()
+    },
+
+    hddressChange() {
+      if (this.calcPwdMD5() !== this.pwdMD5 || this.$refs.hddress === undefined) {
+        return
+      }
+      this.$refs.hddress.prePackQuery()
+    },
   },
   async mounted() {
-    const rst = await Chain.getAll()
+    const rst = await Chain.getAll(null)
 
     if (rst.state) {
       this.$msgkit.normal(rst.msg)

+ 128 - 0
src/components/Hddress.vue

@@ -0,0 +1,128 @@
+<template>
+  <v-card elevation="1">
+    <!-- 顶部组件 -->
+    <Top :query='query' :page='page' :table='table' :visible="editDialog.visible"></Top>
+
+    <!-- 中间表格组件 -->
+    <Table :query='query' :page='page' :table='table' :addressModel="addressModel" :explorer="chain.explorer"
+           :editDialog="editDialog" ref="table"></Table>
+  </v-card>
+</template>
+
+<script>
+import Top from '@/components/viewer/hddress/Top'
+import Table from '@/components/viewer/hddress/Table'
+import TxModel from '@/plugins/model/TxModel'
+import AddressModel from "@/plugins/model/AddressModel";
+
+export default {
+  name: 'Hddress',
+  components: {Top, Table},
+  props: ['chain'],
+  data: () => ({
+    addressModel: 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: 'Hddress Page'
+    },
+    table: {
+      search: '',
+      loading: false,
+      groupBy: 'chainId',
+      groupDesc: true,
+      sortBy: ['chainId', 'index'],
+      sortDesc: [true, false],
+      pageSize: process.env.NODE_ENV === 'development' ? 100 : 200,
+      pageNum: 1,
+      pageLength:99,
+      data: [],
+      headers: [
+        {text: 'Option', value: 'option', width: '5%'},
+        {text: 'ChainId', value: 'chainId', width: '5%'},
+        {text: 'Hash', value: 'hash', width: '10%'},
+        {text: "Other", value: 'other'},
+        {text: 'Name', value: 'name', width: '10%'},
+        {text: 'Type', value: 'type'},
+        {text: 'From', value: 'from'},
+        {text: 'AppendTimestamp', value: 'appendTimestamp'},
+        {text: 'UpdateTimestamp', value: 'updateTimestamp'},
+        {text: 'Comment', value: 'comment'},
+      ]
+    },
+  }),
+  methods: {
+    // 获取数据
+    async generateTableData() {
+      this.createAddressModel()
+
+      this.table.loading = true
+      this.table.data.length = 0
+
+      const rst = await this.addressModel.find(this.table.pageNum, this.table.pageSize)
+
+
+      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)
+      }
+
+      this.table.loading = false
+    },
+    createAddressModel() {
+      if (!this.addressModel) this.addressModel = new AddressModel(this.chain.id, TxModel.MODULES.ADDRESS)
+    },
+    //设置 新增按钮编辑
+    showVisible() {
+      this.editDialog.item = {hash: '', name: ''}
+      this.editDialog.visible = true
+      this.editDialog.addOrUpdate = true
+    },
+
+    async prePackQuery() {
+      // await this.$refs.table.remButClick()
+    },
+    async packQuery() {
+      await this.prePackQuery()//查询数据之前移除 多余的临时标签
+      await this.generateTableData()//拿到数据
+      await this.afterPackQuery()//添加标签 ,添加点击按钮事件
+    },
+    async afterPackQuery() {
+    },
+
+  },
+  provide() {
+    return {
+      packQuery: this.packQuery,
+      showVisible: this.showVisible
+    }
+  },
+  async mounted() {
+    await this.packQuery()
+  }
+}
+</script>

+ 18 - 9
src/components/History.vue

@@ -9,8 +9,8 @@
 </template>
 
 <script>
-import Top from '@/components/viewer/Top'
-import Table from '@/components/viewer/Table'
+import Top from '@/components/viewer/history/Top'
+import Table from '@/components/viewer/history/Table'
 import TxModel from '@/plugins/model/TxModel'
 
 export default {
@@ -22,7 +22,7 @@ export default {
     query: {
       tx: {
         block: '',
-        hash: '0x1a97a8fe340933a8b050dd3d1c823116d31178a253a74ea5a42ea909b37901c4',
+        hash: process.env.NODE_ENV === 'development' ? '0x1a97a8fe340933a8b050dd3d1c823116d31178a253a74ea5a42ea909b37901c4' : '',
         from: '',
         to: ''
       },
@@ -45,6 +45,7 @@ export default {
       sortDesc: [true, false],
       pageSize: process.env.NODE_ENV === 'development' ? 100 : 200,
       pageNum: 1,
+      pageLength:99,
       data: [],
       headers: [
         {text: "Option", value: 'option'},
@@ -75,9 +76,7 @@ export default {
       const rst = await this.tx.find(this.query, this.table.pageNum, this.table.pageSize)
 
       if (rst.state) {
-        console.log("data--:",rst)
         this.table.data = TxModel.parseLocalRecordList(rst.data)
-        console.log("data--_:", this.table.data)
         this.$msgkit.success(rst.msg)
       } else {
         this.$msgkit.error(rst.msg)
@@ -88,16 +87,26 @@ export default {
     createTx() {
       if (!this.tx) this.tx = new TxModel(this.chain.id, TxModel.MODULES.HISTORY)
     },
+    async prePackQuery() {
+      await this.$refs.table.remButRegroup()
+    },
+    async packQuery() {
+      await this.prePackQuery()//查询数据之前移除 多余的临时标签
+      await this.generateTableData()//拿到数据
+      await this.afterPackQuery()//添加标签 ,添加点击按钮事件
+    },
+    async afterPackQuery() {
+      await this.$refs.table.addButRegroup(this.chain.explorer)
+      await this.$refs.table.addButClick(this.chain.explorer)
+    },
   },
   provide() {
     return {
-      generateTableData: this.generateTableData
+      packQuery: this.packQuery
     }
   },
   async mounted() {
-    await this.$refs.table.remButRegroup()
-    await this.generateTableData()
-    await this.$refs.table.addButRegroup()
+    await this.packQuery()
   }
 }
 </script>

+ 18 - 6
src/components/Pending.vue

@@ -9,8 +9,8 @@
 </template>
 
 <script>
-import Top from '@/components/viewer/Top'
-import Table from '@/components/viewer/Table'
+import Top from '@/components/viewer/pending/Top'
+import Table from '@/components/viewer/pending/Table'
 import TxModel from '@/plugins/model/TxModel'
 
 export default {
@@ -45,6 +45,7 @@ export default {
       sortDesc: [true, true, false],
       pageSize: process.env.NODE_ENV === 'development' ? 20 : 200,
       pageNum: 1,
+      pageLength:99,
       data: [],
       headers: [
         {text: "Option", value: 'option'},
@@ -84,15 +85,26 @@ export default {
     },
     createTx() {
       if (!this.tx) this.tx = new TxModel(this.chain.id, TxModel.MODULES.PENDING)
-    }
+    },
+    async prePackQuery() {
+      // await this.$refs.table.remButRegroupAll()
+      // await this.$refs.table.remButClick()
+    },
+    async packQuery() {
+      await this.prePackQuery()//查询数据之前移除 多余的临时标签
+      await this.generateTableData()//拿到数据
+      await this.afterPackQuery()//添加标签 ,添加点击按钮事件
+    },
+    async afterPackQuery() {
+    },
   },
   provide() {
     return {
-      generateTableData: this.generateTableData
+      packQuery: this.packQuery
     }
   },
-  async mounted () {
-    await this.generateTableData()
+  async mounted() {
+    await this.packQuery()
   }
 }
 </script>

+ 2 - 2
src/components/hl/houliang.vue

@@ -78,9 +78,9 @@ export default {
         this.table.data = TxModel.parseLocalRecordList(rst.data)
         this.$msgkit.success(rst.msg)
       } else {
-        this.$msgkit.error(rst.msg)
+        this.$msgkit.error(rst.
+msg)
       }
-
       this.table.loading = false
     },
     createTx() {

+ 31 - 38
src/components/viewer/Table.vue

@@ -255,8 +255,8 @@ import jquery from 'jquery'
 export default {
   name: 'Table',
   components: {BooleanViewer, TradeInfo},
-  props: ['query', 'page', 'table', 'tx', 'explorer'],
-  inject: ['generateTableData'],
+  props: ['query', 'page', 'table', 'tx'],
+  inject: ['packQuery'],
   data: () => ({
     hashKit: HashKit,
     httpKit: HttpKit,
@@ -271,7 +271,7 @@ export default {
     },
     tempPage: 1,
     addressModel: undefined,
-    dblclickRowUrl:"https://tools.blocksec.com/tx/eth/",
+    dblclickRowUrl: "https://tools.blocksec.com/tx/eth/",
   }),
   methods: {
     async deleteByHash(hash_code, item) {
@@ -331,8 +331,7 @@ export default {
       this.table.data = []
 
 
-      await this.generateTableData()
-      await this.addButRegroup()
+      await this.packQuery()
     },
     formatTimeBySixBitTimestamp(sixBitTimestamp) {
       let lastThreeBit = (sixBitTimestamp + '').slice(-3)
@@ -349,15 +348,6 @@ export default {
 
     currentItems(val) {
       console.log("currentItems:", val)
-      if(val.length == 0){
-        this.showIfHide(true)
-        console.log("currentItems:---")
-
-      }else {
-        this.showIfHide(false)
-        console.log("currentItems:+++++++++++")
-
-      }
     },
     input(val) {
       console.log("input:", val)
@@ -368,8 +358,13 @@ export default {
     pageCount(val) {
       console.log("pageCount:", val)
     },
-    pagination(val) {/*12312312312*/
+    pagination(val) {
       console.log("pagination:", val)
+      if (val.itemsLength === 0) {
+        this.remButRegroupAll()
+      } else {
+        this.addButRegroup()
+      }
     },
     toggleSelectAll(val) {
       console.log("toggleSelectAll:", val)
@@ -416,35 +411,32 @@ export default {
     toggle(val) {
       console.log("toggle:", val)
     },
-    dblclickRow(event,value){
-      console.log("dblclickRow:", event,value)
+    dblclickRow(event, value) {
+      console.log("dblclickRow:", event, value)
     },
 
 
     //赋予按钮第二个点击事件
     addButClick(explorer) {
       jquery("div.v-data-table__wrapper table tbody").on("click", ".v-row-group__header td.text-start", function (dom) {
+        console.log("当前访问:",this.tx,dom)
         //需要移除临时 并已经作废的标签
         var array_del = jquery("a.temporary_label")
         for (var d = 0; d < array_del.length; d++) {
           var a = array_del.eq(d)[0]
           var tr = jquery(jquery(a).parent().parent())[0]
           var className = tr.className
-          console.log("addButRegroup--移除---", a,)
-          if(className.indexOf("v-row-group__header") === -1){
+          if (className.indexOf("v-row-group__header") === -1) {
             jquery(a).remove()
           }
         }
 
         var array = jquery("div.v-data-table__wrapper table tbody .v-row-group__header td.text-start");
-        console.log("addButRegroup-监听-----", array)
         for (var i = 0; i < array.length; i++) {
-          console.log("index:", array.eq(i))
           //思路:根据 jq 拿到对应的td 然后循环遍历  动态添加元素标签
           var td = array.eq(i)[0]
           var innerText = td.innerText
           var but_x = jquery(td).children()[1]
-          console.log("addButRegroup--对象获取---", but_x)
           if (jquery(td).children().length > 2) {
 
           } else {
@@ -457,48 +449,50 @@ export default {
       })
     },
     //添加跳转标签
-    addButRegroup() {
+    addButRegroup(explorer) {
       var array = jquery("div.v-data-table__wrapper table tbody .v-row-group__header td.text-start");
-      console.log("addButRegroup-监听-----", array)
+      console.log(array)
       for (var i = 0; i < array.length; i++) {
-        console.log("index:", array.eq(i))
         //思路:根据 jq 拿到对应的td 然后循环遍历  动态添加元素标签
         var td = array.eq(i)[0]
         var innerText = td.innerText
 
         var but_x = jquery(td).children()[1]
-        console.log("addButRegroup--对象获取---", but_x)
         if (jquery(td).children().length > 2) {
 
         } else {
           //添加
-          var href = this.explorer + "/block/" + innerText.split(": ")[1];
+          var href = explorer + "/block/" + innerText.split(": ")[1];
           var a = '<a class="temporary_label" href="' + href + '" target="_blank"> 跳转 </a>';
           jquery(but_x).before(a)
         }
       }
     },
-    //移除
+    //需要移除临时 并已经作废的标签
     remButRegroup() {
-      //需要移除临时 并已经作废的标签
       var array_del = jquery("a.temporary_label")
       for (var d = 0; d < array_del.length; d++) {
         var a = array_del.eq(d)[0]
         var tr = jquery(jquery(a).parent().parent())[0]
         var className = tr.className
-        console.log("addButRegroup--移除---", a,)
-        if(className.indexOf("v-row-group__header") === -1){
+        if (className.indexOf("v-row-group__header") === -1) {
           jquery(a).remove()
         }
       }
     },
-    showIfHide(boo){
-     var array =  jquery("a.temporary_label")
-      console.log("array",array)
-      if(array.length > 0){
+    remButRegroupAll() {
+      var array_del = jquery("a.temporary_label")
+      for (var d = 0; d < array_del.length; d++) {
+        var a = array_del.eq(d)[0]
+        jquery(a).remove()
+      }
+    },
+
+    showIfHide(boo) {
+      var array = jquery("a.temporary_label")
+      if (array.length > 0) {
         var a = array[0]
-        console.log("a",a)
-        if(boo)
+        if (boo)
           jquery(a).hide()
         else
           jquery(a).show()
@@ -508,7 +502,6 @@ export default {
 
   },
   async mounted() {
-    this.addButClick(this.explorer)
     // let vm = new Vue({
     //   el: "td.text-start",
     //   click: () => {

+ 2 - 2
src/components/viewer/Top.vue

@@ -62,10 +62,10 @@
 export default {
   name: 'Top',
   props: ['query', 'page', 'table'],
-  inject: ['generateTableData'],
+  inject: ['packQuery'],
   methods: {
     async generateTableDataAgain() {
-      await this.generateTableData()
+      await this.packQuery()
     }
   },
   async mounted() {

+ 263 - 0
src/components/viewer/hddress/Table.vue

@@ -0,0 +1,263 @@
+<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
+                    :group-by="table.groupBy" :group-desc="table.groupDesc"
+                    :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.chainId="{ item }">
+          <div>
+            {{ item.chainId }}
+          </div>
+        </template>
+        <template v-slot:item.hash="{ item }">
+          <v-btn v-if="item.hash.indexOf('0x') !== -1" outlined text target="_blank"
+                 @click="">
+            {{ hashKit.head5(item.hash) + '..' }}
+          </v-btn>
+          <v-chip v-else label target="_blank">
+            {{ hashKit.head5(item.hash) + '..' }}
+          </v-chip>
+        </template>
+        <template v-slot:item.other="{ item }">
+          <div v-if="item.other !== undefined && item.other !== '' && item.other !== null">
+            {{ item.other }}
+          </div>
+          <div v-else>
+            [other]
+          </div>
+        </template>
+        <template v-slot:item.name="{ item }">
+          <div v-if="item.name !== undefined && item.name !== '' && item.name !== null">
+            {{ item.name }}
+          </div>
+          <div v-else>
+            [name]
+          </div>
+        </template>
+        <template v-slot:item.type="{ item }">
+          <div v-if="item.type !== undefined && item.type !== '' && item.type !== null">
+            {{ item.type }}
+          </div>
+          <div v-else>
+            [type]
+          </div>
+        </template>
+        <template v-slot:item.from="{ item }">
+          <div v-if="item.from !== undefined && item.from !== '' && item.from !== null">
+            {{ item.from }}
+          </div>
+          <div v-else>
+            [from]
+          </div>
+        </template>
+        <template v-slot:item.appendTimestamp="{ item }">
+          <div>
+            {{ timeKit.getTimeByMillisecond(item.appendTimestamp) }}
+          </div>
+        </template>
+        <template v-slot:item.updateTimestamp="{ item }">
+          <div>
+            {{  timeKit.getTimeByMillisecond(item.updateTimestamp) }}
+          </div>
+        </template>
+        <template v-slot:item.comment="{ item }">
+          <div v-if="item.comment !== undefined && item.comment !== '' && item.comment !== null">
+            {{ item.comment }}
+          </div>
+          <div v-else>
+            [comment]
+          </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 v-if="editDialog.addOrUpdate" label="hash" v-model="editDialog.item.hash"></v-text-field>
+              <v-text-field v-else disabled label="hash" v-model="editDialog.item.hash"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="name" v-model="editDialog.item.name"></v-text-field>
+            </v-col>
+          </v-row>
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn text color="primary" @click="addOrUpdateAddress">提交</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+  </div>
+</template>
+
+<script>
+import TradeInfo from '@/components/viewer/hddress/table/TradeInfoDetails'
+import BooleanViewer from '@/components/viewer/hddress/table/BooleanViewer'
+import HashKit from '@/plugins/kit/HashKit'
+import HttpKit from '@/plugins/kit/HttpKit'
+import TimeKit from '@/plugins/kit/TimeKit'
+import AddressModel from "@/plugins/model/AddressModel";
+import jquery from 'jquery'
+
+export default {
+  name: 'Table',
+  components: {BooleanViewer, TradeInfo},
+  props: ['query', 'page', 'table', 'addressModel', '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 addOrUpdateAddress() {
+      // this.createAddressModel()
+
+      const {hash, name} = this.editDialog.item
+      const updateOrAdd = {hash, name}
+      const rst1 = await this.addressModel.updateAddressBaseModel(updateOrAdd)
+      if (rst1.state) {
+        this.$msgkit.success((this.editDialog.addOrUpdate ? '添加' : '更新') + '成功')
+
+        this.editDialog.visible = false
+      } 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
+    },
+    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')
+      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/hddress/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/hddress/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/hddress/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>

+ 552 - 0
src/components/viewer/history/Table.vue

@@ -0,0 +1,552 @@
+<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
+                    class="history-table"
+                    @current-items="currentItems"
+                    @input="input"
+                    @item-selected="itemSelected"
+                    @item-expanded="itemExpanded"
+                    @page-count="pageCount"
+                    @pagination="pagination"
+                    @toggle-select-all="toggleSelectAll"
+                    @update:expanded="updateExpanded"
+                    @update:group-by="updateGroupBy"
+                    @update:group-desc="updateGroupEesc"
+                    @update:items-per-page="updateItemsPerPage"
+                    @update:multi-sort="updateMultiSort"
+                    @update:must-sort="updateMustSort"
+                    @update:options="updateOptions"
+                    @update:page="updatePage"
+                    @update:sort-by="updateSortBy"
+                    @update:sort-desc="updateSortDesc"
+                    @toggle="toggle"
+
+                    @dblclick:row="dblclickRow"
+                    :group-by="table.groupBy" :group-desc="table.groupDesc"
+                    :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>
+        <!-- Block -->
+        <template v-slot:item.block="{ item }">
+          <div>
+            {{ item.block }}
+          </div>
+        </template>
+        <!-- Hash -->
+        <template v-slot:item.hash="{ item }">
+          <v-btn v-if="item.hash.indexOf('0x') !== -1" outlined text target="_blank"
+                 @click="httpKit.jumpToExplorer(item.hash, 'tx')">
+            {{ hashKit.head5(item.hash) + '..' }}
+          </v-btn>
+          <v-chip v-else label target="_blank">
+            {{ hashKit.head5(item.hash) + '..' }}
+          </v-chip>
+        </template>
+        <!-- From -->
+        <template v-slot:item.from="{ item }">
+          <v-chip v-if="!item.fromName" label color="indigo lighten-3" @click="httpKit.jumpToExplorer(item.to)">
+            {{ hashKit.headAndEnd2(item.from) }}
+          </v-chip>
+          <v-chip v-else label color="green lighten-3" target="_blank" @click="httpKit.jumpToExplorer(item.from)">
+            {{ item.fromName }}
+          </v-chip>
+        </template>
+        <!-- to -->
+        <template v-slot:item.to="{ item }">
+          <v-chip v-if="!item.toName" label color="indigo lighten-4" @click="httpKit.jumpToExplorer(item.to)">
+            {{ hashKit.headAndEnd2(item.to) }}
+          </v-chip>
+          <v-chip v-else label color="indigo lighten-4" @click="httpKit.jumpToExplorer(item.to)">
+            {{ item.toName }}
+          </v-chip>
+        </template>
+        <template v-slot:item.type="{ item }">
+          <div v-if="item.type !== undefined && item.type !== ''">
+            {{ item.type }}
+          </div>
+          <div v-else>
+            [type]
+          </div>
+        </template>
+        <template v-slot:item.index="{ item }">
+          <div v-if="item.index !== undefined">
+            {{ item.index }}
+          </div>
+          <div v-else>
+            [index]
+          </div>
+        </template>
+        <template v-slot:item.status="{ item }">
+          <div v-if="item.status !== undefined">
+            {{ item.status }}
+          </div>
+          <div v-else>
+            [status]
+          </div>
+        </template>
+        <template v-slot:item.ping="{ item }">
+          <div v-if="item.ping !== undefined">
+            {{ item.ping }}
+          </div>
+          <div v-else>
+            [ping]
+          </div>
+        </template>
+        <template v-slot:item.isMev="{ item }">
+          <BooleanViewer :value="item.isMev"></BooleanViewer>
+        </template>
+        <template v-slot:item.isBot="{ item }">
+          <BooleanViewer :value="item.isBot"></BooleanViewer>
+        </template>
+        <template v-slot:item.maybeBot="{ item }">
+          <BooleanViewer :value="item.maybeBot"></BooleanViewer>
+        </template>
+        <template v-slot:item.timestamp="{ item }">
+          {{ formatTimeBySixBitTimestamp(item.timestamp) }}
+        </template>
+        <template v-slot:item.comment="{ item }">
+          <div v-if="item.comment">
+            <span class="memo-span">{{ item.comment }}</span>
+          </div>
+          <div v-else>
+            [comment]
+          </div>
+        </template>
+        <!--tradeInfo-->
+        <template v-slot:item.transferList="{ item }">
+          <div v-if="page.name === 'Pending Page'">
+            <div class="tradeInfoBtn" @click="showTradeInfo(item)">
+              <v-chip v-for="tokenAddress in item.tokenAddressList" :key='tokenAddress'
+                      :color="item.tokenMap[tokenAddress] ? hashKit.generateColorByHash(tokenAddress) : undefined"
+                      class="tokenChip">
+                {{
+                  item.tokenMap[tokenAddress] ? item.tokenMap[tokenAddress] : '**' + hashKit.headAndEnd2(tokenAddress)
+                }}
+              </v-chip>
+            </div>
+          </div>
+          <div v-else>
+            <TradeInfo :item="item"></TradeInfo>
+          </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>
+
+    <!-- 展示transfer的详情 -->
+    <v-dialog v-model="transferDetailsDialog.visible" max-width="800">
+      <v-card elevation="0">
+        <v-card-title>交易详情</v-card-title>
+        <v-card-text>
+          <TradeInfo :item="transferDetailsDialog.data"></TradeInfo>
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn text color="primary" @click="transferDetailsDialog.visible = false">我知道了</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+
+    <!-- 编辑框 -->
+    <v-dialog v-model="editDialog.visible" max-width="1200">
+      <v-card elevation="0">
+        <v-card-title>编辑详情</v-card-title>
+        <v-card-text>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field disabled label="hash" v-model="editDialog.item.hash"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="comment" v-model="editDialog.item.comment"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="6">
+              <v-text-field disabled label="from" v-model="editDialog.item.from"></v-text-field>
+            </v-col>
+            <v-col cols="6">
+              <v-text-field label="fromName" v-model="editDialog.item.fromName"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="6">
+              <v-text-field disabled label="to" v-model="editDialog.item.to"></v-text-field>
+            </v-col>
+            <v-col cols="6">
+              <v-text-field label="toName" v-model="editDialog.item.toName"></v-text-field>
+            </v-col>
+          </v-row>
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn text color="primary" @click="updateAdapter">提交编辑</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+  </div>
+</template>
+
+<script>
+import TradeInfo from '@/components/viewer/history/table/TradeInfoDetails'
+import BooleanViewer from '@/components/viewer/history/table/BooleanViewer'
+import HashKit from '@/plugins/kit/HashKit'
+import HttpKit from '@/plugins/kit/HttpKit'
+import TimeKit from '@/plugins/kit/TimeKit'
+import AddressModel from "@/plugins/model/AddressModel";
+import jquery from 'jquery'
+
+export default {
+  name: 'Table',
+  components: {BooleanViewer, TradeInfo},
+  props: ['query', 'page', 'table', 'tx'],
+  inject: ['packQuery'],
+  data: () => ({
+    hashKit: HashKit,
+    httpKit: HttpKit,
+    timeKit: TimeKit,
+    transferDetailsDialog: {
+      data: [],
+      visible: false
+    },
+    editDialog: {
+      item: {},
+      visible: false
+    },
+    tempPage: 1,
+    addressModel: undefined,
+    dblclickRowUrl: "https://tools.blocksec.com/tx/eth/",
+  }),
+  methods: {
+    async deleteByHash(hash_code, item) {
+      if (confirm('要删吗?\n' + hash_code)) {
+        console.log(JSON.stringify(item))
+        this.$msgkit.warning('还没有做,别急啊')
+        // const rst = await EthMev.deleteByHash(hash_code)
+        //
+        // if (rst.data.state) {
+        //   this.$msgkit.success(rst.data.msg)
+        //
+        //   await this.pullData()
+        // } else {
+        //   this.$msgkit.error(rst.data.msg)
+        // }
+      }
+    },
+    async updateAdapter() {
+      this.createAddressModel()
+
+      const updateItem = this.editDialog.item
+      const txBaseItem = {
+        hash: updateItem.hash,
+        comment: updateItem.comment
+      }
+      const fromAddrBaseItem = {
+        hash: updateItem.from,
+        name: updateItem.fromName
+      }
+      const toAddrBaseItem = {
+        hash: updateItem.to,
+        name: updateItem.toName
+      }
+
+      const rst1 = await this.tx.updateTxBaseModel(txBaseItem)
+      const rst2 = await this.addressModel.updateAddressBaseModel(fromAddrBaseItem)
+      const rst3 = await this.addressModel.updateAddressBaseModel(toAddrBaseItem)
+
+      if (rst1.state && rst2.state && rst3.state) {
+        this.$msgkit.success('更新成功')
+
+        this.editDialog.visible = false
+      } else {
+        this.$msgkit.error(`rst1: ${rst1.msg}, rst2: ${rst2.msg}, rst3: ${rst3.msg}`)
+      }
+    },
+    showTradeInfo(item) {
+      this.transferDetailsDialog.data = item
+      this.transferDetailsDialog.visible = true
+    },
+    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
+    },
+    createAddressModel() {
+      if (!this.addressModel) this.addressModel = new AddressModel(this.tx.chainId, AddressModel.MODULES.unknown)
+    },
+
+
+    currentItems(val) {
+      console.log("currentItems:", val)
+    },
+    input(val) {
+      console.log("input:", val)
+    },
+    itemSelected(val) {
+      console.log("itemSelected:", val)
+    },
+    pageCount(val) {
+      console.log("pageCount:", val)
+    },
+    pagination(val) {
+      console.log("pagination:", val)
+      if (val.itemsLength === 0) {
+        this.remButRegroupAll()
+      }
+
+      if (val.itemsLength > 0) {
+        this.addButRegroup()
+      }
+    },
+    toggleSelectAll(val) {
+      console.log("toggleSelectAll:", val)
+    },
+    updateExpanded(val) {
+      console.log("updateExpanded:", val)
+    },
+    updateGroupBy(val) {
+      console.log("updateGroupBy:", val)
+    },
+    updateGroupEesc(val) {
+      console.log("updateGroupEesc:", val)
+    },
+    updateItemsPerPage(val) {
+      console.log("updateItemsPerPage:", val)
+    },
+
+    updateMultiSort(val) {
+      console.log("updateMultiSort:", val)
+    },
+
+    updateMustSort(val) {
+      console.log("updateMustSort:", val)
+    },
+
+    updateOptions(val) {
+      console.log("updateOptions:", val)
+    },
+
+    updatePage(val) {
+      console.log("updatePage:", val)
+    },
+    updateSortBy(val) {
+      console.log("updateSortBy:", val)
+    },
+    updateSortDesc(val) {
+      console.log("updateSortDesc:", val)
+    },
+
+    itemExpanded(item, val) {
+      console.log("itemExpanded:", item, val)
+    },
+
+    toggle(val) {
+      console.log("toggle:", val)
+    },
+    dblclickRow(event, value) {
+      console.log("dblclickRow:", event, value)
+    },
+
+
+    //赋予按钮第二个点击事件
+    addButClick(explorer) {
+      jquery(".history-table div.v-data-table__wrapper table tbody").on("click", ".v-row-group__header td.text-start", function (dom) {
+        console.log("当前访问:", this.tx, dom)
+        //需要移除临时 并已经作废的标签
+        var array_del = jquery("a.temporary_label")
+        for (var d = 0; d < array_del.length; d++) {
+          var a = array_del.eq(d)[0]
+          var tr = jquery(jquery(a).parent().parent())[0]
+          var className = tr.className
+          if (className.indexOf("v-row-group__header") === -1) {
+            jquery(a).remove()
+          }
+        }
+
+        var array = jquery(".history-table div.v-data-table__wrapper table tbody .v-row-group__header td.text-start");
+        for (var i = 0; i < array.length; i++) {
+          //思路:根据 jq 拿到对应的td 然后循环遍历  动态添加元素标签
+          var td = array.eq(i)[0]
+          var innerText = td.innerText
+          var but_x = jquery(td).children()[1]
+          if (jquery(td).children().length > 2) {
+
+          } else {
+            //添加
+            var href = explorer + "/block/" + innerText.split(": ")[1];
+            var a = '<a  class="temporary_label" href="' + href + '" target="_blank"> 跳转 </a>';
+            jquery(but_x).before(a)
+          }
+        }
+      })
+    },
+
+    //添加跳转标签
+    addButRegroup(explorer) {
+      var array = jquery(".history-table div.v-data-table__wrapper table tbody .v-row-group__header td.text-start");
+      console.log(array)
+      for (var i = 0; i < array.length; i++) {
+        //思路:根据 jq 拿到对应的td 然后循环遍历  动态添加元素标签
+        var td = array.eq(i)[0]
+        var innerText = td.innerText
+
+        var but_x = jquery(td).children()[1]
+        if (jquery(td).children().length > 2) {
+
+        } else {
+          //添加
+          var href = explorer + "/block/" + innerText.split(": ")[1];
+          var a = '<a class="temporary_label" href="' + href + '" target="_blank"> 跳转 </a>';
+          jquery(but_x).before(a)
+        }
+      }
+    },
+    //需要移除临时 并已经作废的标签
+    remButRegroup() {
+      var array_del = jquery("a.temporary_label")
+      for (var d = 0; d < array_del.length; d++) {
+        var a = array_del.eq(d)[0]
+        var tr = jquery(jquery(a).parent().parent())[0]
+        var className = tr.className
+        if (className.indexOf("v-row-group__header") === -1) {
+          jquery(a).remove()
+        }
+      }
+    },
+    remButRegroupAll() {
+      var array_del = jquery("a.temporary_label")
+      for (var d = 0; d < array_del.length; d++) {
+        var a = array_del.eq(d)[0]
+        jquery(a).remove()
+      }
+    },
+
+    showIfHide(boo) {
+      var array = jquery("a.temporary_label")
+      if (array.length > 0) {
+        var a = array[0]
+        if (boo)
+          jquery(a).hide()
+        else
+          jquery(a).show()
+      }
+
+    },
+
+  },
+  async mounted() {
+    // let vm = new Vue({
+    //   el: "td.text-start",
+    //   click: () => {
+    //     console.log('点击')
+    //   }
+    // });
+
+    // $("td.text-start").on("click",this,function (){
+    //   console.log('点击')
+    // });
+  }
+}
+</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>

+ 93 - 0
src/components/viewer/history/Top.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="top-container">
+    <v-row>
+      <!-- tx信息过滤 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.Hash" v-model="query.tx.hash" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.From" v-model="query.tx.from" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.To" v-model="query.tx.to" />
+      </v-col>
+      <!-- block过滤 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.BlockNumber" v-model="query.tx.block" />
+      </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-col>
+    </v-row>
+
+    <v-row>
+      <!-- transfer信息过滤 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="Transfer.Token" v-model="query.transfer.token" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Transfer.From" v-model="query.transfer.from" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Transfer.To" v-model="query.transfer.to" />
+      </v-col>
+
+      <!-- 数据刷新时间 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="自定义刷新时间" v-model="query.autoFlushTime" />
+      </v-col>
+
+      <!-- 自动刷新提示 -->
+      <v-col v-if='query.autoFlushTime === 0' cols="18" md="2">
+        <v-btn outlined x-large tile color="teal" @click='query.autoFlushTime = 20'>
+          <v-icon left>mdi-clock</v-icon>
+          以20s/次刷新
+        </v-btn>
+      </v-col>
+
+      <!-- 自动刷新提示 -->
+      <v-col v-else-if='query.autoFlushTime !== 0' cols="18" md="2">
+        <v-btn outlined x-large tile color="red" @click='query.autoFlushTime = 0'>停止刷新</v-btn>
+      </v-col>
+    </v-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Top',
+  props: ['query', 'page', 'table'],
+  inject: ['packQuery'],
+  methods: {
+    async generateTableDataAgain() {
+      await this.packQuery()
+    }
+  },
+  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/history/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/history/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>

+ 470 - 0
src/components/viewer/pending/Table.vue

@@ -0,0 +1,470 @@
+<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
+                    @current-items="currentItems"
+                    @input="input"
+                    @item-selected="itemSelected"
+                    @item-expanded="itemExpanded"
+                    @page-count="pageCount"
+                    @pagination="pagination"
+                    @toggle-select-all="toggleSelectAll"
+                    @update:expanded="updateExpanded"
+                    @update:group-by="updateGroupBy"
+                    @update:group-desc="updateGroupEesc"
+                    @update:items-per-page="updateItemsPerPage"
+                    @update:multi-sort="updateMultiSort"
+                    @update:must-sort="updateMustSort"
+                    @update:options="updateOptions"
+                    @update:page="updatePage"
+                    @update:sort-by="updateSortBy"
+                    @update:sort-desc="updateSortDesc"
+                    @toggle="toggle"
+
+                    @dblclick:row="dblclickRow"
+                    :group-by="table.groupBy" :group-desc="table.groupDesc"
+                    :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>
+        <!-- Block -->
+        <template v-slot:item.block="{ item }">
+          <div>
+            {{ item.block }}
+          </div>
+        </template>
+        <!-- Hash -->
+        <template v-slot:item.hash="{ item }">
+          <v-btn v-if="item.hash.indexOf('0x') !== -1" outlined text target="_blank"
+                 @click="httpKit.jumpToExplorer(item.hash, 'tx')">
+            {{ hashKit.head5(item.hash) + '..' }}
+          </v-btn>
+          <v-chip v-else label target="_blank">
+            {{ hashKit.head5(item.hash) + '..' }}
+          </v-chip>
+        </template>
+        <!-- From -->
+        <template v-slot:item.from="{ item }">
+          <v-chip v-if="!item.fromName" label color="indigo lighten-3" @click="httpKit.jumpToExplorer(item.to)">
+            {{ hashKit.headAndEnd2(item.from) }}
+          </v-chip>
+          <v-chip v-else label color="green lighten-3" target="_blank" @click="httpKit.jumpToExplorer(item.from)">
+            {{ item.fromName }}
+          </v-chip>
+        </template>
+        <!-- to -->
+        <template v-slot:item.to="{ item }">
+          <v-chip v-if="!item.toName" label color="indigo lighten-4" @click="httpKit.jumpToExplorer(item.to)">
+            {{ hashKit.headAndEnd2(item.to) }}
+          </v-chip>
+          <v-chip v-else label color="indigo lighten-4" @click="httpKit.jumpToExplorer(item.to)">
+            {{ item.toName }}
+          </v-chip>
+        </template>
+        <template v-slot:item.type="{ item }">
+          <div v-if="item.type !== undefined && item.type !== ''">
+            {{ item.type }}
+          </div>
+          <div v-else>
+            [type]
+          </div>
+        </template>
+        <template v-slot:item.index="{ item }">
+          <div v-if="item.index !== undefined">
+            {{ item.index }}
+          </div>
+          <div v-else>
+            [index]
+          </div>
+        </template>
+        <template v-slot:item.status="{ item }">
+          <div v-if="item.status !== undefined">
+            {{ item.status }}
+          </div>
+          <div v-else>
+            [status]
+          </div>
+        </template>
+        <template v-slot:item.ping="{ item }">
+          <div v-if="item.ping !== undefined">
+            {{ item.ping }}
+          </div>
+          <div v-else>
+            [ping]
+          </div>
+        </template>
+        <template v-slot:item.isMev="{ item }">
+          <BooleanViewer :value="item.isMev"></BooleanViewer>
+        </template>
+        <template v-slot:item.isBot="{ item }">
+          <BooleanViewer :value="item.isBot"></BooleanViewer>
+        </template>
+        <template v-slot:item.maybeBot="{ item }">
+          <BooleanViewer :value="item.maybeBot"></BooleanViewer>
+        </template>
+        <template v-slot:item.timestamp="{ item }">
+          {{ formatTimeBySixBitTimestamp(item.timestamp) }}
+        </template>
+        <template v-slot:item.comment="{ item }">
+          <div v-if="item.comment">
+            <span class="memo-span">{{ item.comment }}</span>
+          </div>
+          <div v-else>
+            [comment]
+          </div>
+        </template>
+        <!--tradeInfo-->
+        <template v-slot:item.transferList="{ item }">
+          <div v-if="page.name === 'Pending Page'">
+            <div class="tradeInfoBtn" @click="showTradeInfo(item)">
+              <v-chip v-for="tokenAddress in item.tokenAddressList" :key='tokenAddress'
+                      :color="item.tokenMap[tokenAddress] ? hashKit.generateColorByHash(tokenAddress) : undefined"
+                      class="tokenChip">
+                {{
+                  item.tokenMap[tokenAddress] ? item.tokenMap[tokenAddress] : '**' + hashKit.headAndEnd2(tokenAddress)
+                }}
+              </v-chip>
+            </div>
+          </div>
+          <div v-else>
+            <TradeInfo :item="item"></TradeInfo>
+          </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>
+
+    <!-- 展示transfer的详情 -->
+    <v-dialog v-model="transferDetailsDialog.visible" max-width="800">
+      <v-card elevation="0">
+        <v-card-title>交易详情</v-card-title>
+        <v-card-text>
+          <TradeInfo :item="transferDetailsDialog.data"></TradeInfo>
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn text color="primary" @click="transferDetailsDialog.visible = false">我知道了</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+
+    <!-- 编辑框 -->
+    <v-dialog v-model="editDialog.visible" max-width="1200">
+      <v-card elevation="0">
+        <v-card-title>编辑详情</v-card-title>
+        <v-card-text>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field disabled label="hash" v-model="editDialog.item.hash"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="comment" v-model="editDialog.item.comment"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="6">
+              <v-text-field disabled label="from" v-model="editDialog.item.from"></v-text-field>
+            </v-col>
+            <v-col cols="6">
+              <v-text-field label="fromName" v-model="editDialog.item.fromName"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="6">
+              <v-text-field disabled label="to" v-model="editDialog.item.to"></v-text-field>
+            </v-col>
+            <v-col cols="6">
+              <v-text-field label="toName" v-model="editDialog.item.toName"></v-text-field>
+            </v-col>
+          </v-row>
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn text color="primary" @click="updateAdapter">提交编辑</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+  </div>
+</template>
+
+<script>
+import TradeInfo from '@/components/viewer/pending/table/TradeInfoDetails'
+import BooleanViewer from '@/components/viewer/pending/table/BooleanViewer'
+import HashKit from '@/plugins/kit/HashKit'
+import HttpKit from '@/plugins/kit/HttpKit'
+import TimeKit from '@/plugins/kit/TimeKit'
+import AddressModel from "@/plugins/model/AddressModel";
+import jquery from 'jquery'
+
+export default {
+  name: 'Table',
+  components: {BooleanViewer, TradeInfo},
+  props: ['query', 'page', 'table', 'tx'],
+  inject: ['packQuery'],
+  data: () => ({
+    hashKit: HashKit,
+    httpKit: HttpKit,
+    timeKit: TimeKit,
+    transferDetailsDialog: {
+      data: [],
+      visible: false
+    },
+    editDialog: {
+      item: {},
+      visible: false
+    },
+    tempPage: 1,
+    addressModel: undefined,
+    dblclickRowUrl: "https://tools.blocksec.com/tx/eth/",
+  }),
+  methods: {
+    async deleteByHash(hash_code, item) {
+      if (confirm('要删吗?\n' + hash_code)) {
+        console.log(JSON.stringify(item))
+        this.$msgkit.warning('还没有做,别急啊')
+        // const rst = await EthMev.deleteByHash(hash_code)
+        //
+        // if (rst.data.state) {
+        //   this.$msgkit.success(rst.data.msg)
+        //
+        //   await this.pullData()
+        // } else {
+        //   this.$msgkit.error(rst.data.msg)
+        // }
+      }
+    },
+    async updateAdapter() {
+      this.createAddressModel()
+
+      const updateItem = this.editDialog.item
+      const txBaseItem = {
+        hash: updateItem.hash,
+        comment: updateItem.comment
+      }
+      const fromAddrBaseItem = {
+        hash: updateItem.from,
+        name: updateItem.fromName
+      }
+      const toAddrBaseItem = {
+        hash: updateItem.to,
+        name: updateItem.toName
+      }
+
+      const rst1 = await this.tx.updateTxBaseModel(txBaseItem)
+      const rst2 = await this.addressModel.updateAddressBaseModel(fromAddrBaseItem)
+      const rst3 = await this.addressModel.updateAddressBaseModel(toAddrBaseItem)
+
+      if (rst1.state && rst2.state && rst3.state) {
+        this.$msgkit.success('更新成功')
+
+        this.editDialog.visible = false
+      } else {
+        this.$msgkit.error(`rst1: ${rst1.msg}, rst2: ${rst2.msg}, rst3: ${rst3.msg}`)
+      }
+    },
+    showTradeInfo(item) {
+      this.transferDetailsDialog.data = item
+      this.transferDetailsDialog.visible = true
+    },
+    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
+    },
+    createAddressModel() {
+      if (!this.addressModel) this.addressModel = new AddressModel(this.tx.chainId, AddressModel.MODULES.unknown)
+    },
+
+
+    currentItems(val) {
+      console.log("currentItems:", val)
+    },
+    input(val) {
+      console.log("input:", val)
+    },
+    itemSelected(val) {
+      console.log("itemSelected:", val)
+    },
+    pageCount(val) {
+      console.log("pageCount:", val)
+    },
+    pagination(val) {
+      console.log("pagination:", val)
+      if (val.itemsLength === 0) {
+      } else {
+      }
+    },
+    toggleSelectAll(val) {
+      console.log("toggleSelectAll:", val)
+    },
+    updateExpanded(val) {
+      console.log("updateExpanded:", val)
+    },
+    updateGroupBy(val) {
+      console.log("updateGroupBy:", val)
+    },
+    updateGroupEesc(val) {
+      console.log("updateGroupEesc:", val)
+    },
+    updateItemsPerPage(val) {
+      console.log("updateItemsPerPage:", val)
+    },
+
+    updateMultiSort(val) {
+      console.log("updateMultiSort:", val)
+    },
+
+    updateMustSort(val) {
+      console.log("updateMustSort:", val)
+    },
+
+    updateOptions(val) {
+      console.log("updateOptions:", val)
+    },
+
+    updatePage(val) {
+      console.log("updatePage:", val)
+    },
+    updateSortBy(val) {
+      console.log("updateSortBy:", val)
+    },
+    updateSortDesc(val) {
+      console.log("updateSortDesc:", val)
+    },
+
+    itemExpanded(item, val) {
+      console.log("itemExpanded:", item, val)
+    },
+
+    toggle(val) {
+      console.log("toggle:", val)
+    },
+    dblclickRow(event, value) {
+      console.log("dblclickRow:", event, value)
+    },
+    remButRegroupAll() {
+      var array_del = jquery("a.temporary_label")
+      for (var d = 0; d < array_del.length; d++) {
+        var a = array_del.eq(d)[0]
+        jquery(a).remove()
+      }
+    },
+    remButClick(){
+      jquery(".v-row-group__header td.text-start").unbind('click')
+    },
+  },
+  async mounted() {
+    // let vm = new Vue({
+    //   el: "td.text-start",
+    //   click: () => {
+    //     console.log('点击')
+    //   }
+    // });
+
+    // $("td.text-start").on("click",this,function (){
+    //   console.log('点击')
+    // });
+  }
+}
+</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>

+ 93 - 0
src/components/viewer/pending/Top.vue

@@ -0,0 +1,93 @@
+<template>
+  <div class="top-container">
+    <v-row>
+      <!-- tx信息过滤 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.Hash" v-model="query.tx.hash" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.From" v-model="query.tx.from" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.To" v-model="query.tx.to" />
+      </v-col>
+      <!-- block过滤 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="Tx.BlockNumber" v-model="query.tx.block" />
+      </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-col>
+    </v-row>
+
+    <v-row>
+      <!-- transfer信息过滤 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="Transfer.Token" v-model="query.transfer.token" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Transfer.From" v-model="query.transfer.from" />
+      </v-col>
+      <v-col cols="18" md="2">
+        <v-text-field required label="Transfer.To" v-model="query.transfer.to" />
+      </v-col>
+
+      <!-- 数据刷新时间 -->
+      <v-col cols="18" md="2">
+        <v-text-field required label="自定义刷新时间" v-model="query.autoFlushTime" />
+      </v-col>
+
+      <!-- 自动刷新提示 -->
+      <v-col v-if='query.autoFlushTime === 0' cols="18" md="2">
+        <v-btn outlined x-large tile color="teal" @click='query.autoFlushTime = 20'>
+          <v-icon left>mdi-clock</v-icon>
+          以20s/次刷新
+        </v-btn>
+      </v-col>
+
+      <!-- 自动刷新提示 -->
+      <v-col v-else-if='query.autoFlushTime !== 0' cols="18" md="2">
+        <v-btn outlined x-large tile color="red" @click='query.autoFlushTime = 0'>停止刷新</v-btn>
+      </v-col>
+    </v-row>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Top',
+  props: ['query', 'page', 'table'],
+  inject: ['packQuery'],
+  methods: {
+    async generateTableDataAgain() {
+      await this.packQuery()
+    }
+  },
+  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/pending/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/pending/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 - 1
src/plugins/kit/HashKit.js

@@ -20,7 +20,7 @@ export default class HashKit {
 
   static generateColorByHash(hash) {
     const hashSub = hash.substring(2, 5)
-    let color = '#ccc'
+    let color = '#history'
 
     if (hashSub !== 'eth') {
       let bit1 = parseInt(hashSub[0], 16)

+ 2 - 2
src/plugins/kit/TimeKit.js

@@ -42,7 +42,7 @@ export default class TimeKit {
    */
   static getTimeByMillisecond = function (timestamp) {
     // 组织日期格式并返回
-    return TickKit.dateFormat('YYYY-mm-dd HH:MM:SS', new Date(timestamp))
+    return TimeKit.dateFormat('YYYY-mm-dd HH:MM:SS', new Date(timestamp))
   }
 
   /**
@@ -53,7 +53,7 @@ export default class TimeKit {
    */
   static getTimeBySecond = function (timestamp) {
     // 组织日期格式并返回
-    return TickKit.dateFormat('YYYY-mm-dd HH:MM:SS', new Date(timestamp * 1000))
+    return TimeKit.dateFormat('YYYY-mm-dd HH:MM:SS', new Date(timestamp * 1000))
   }
 
   static sleep = function (time) {

+ 27 - 14
src/plugins/model/AddressModel.js

@@ -1,23 +1,36 @@
 import http from 'axios'
 
 export default class AddressModel {
-  static MODULES = {
-    unknown: 'unknown'
-  }
+    static MODULES = {
+        unknown: 'unknown'
+    }
 
-  constructor(chainId, module) {
-    if (!chainId || !module) throw "Must have [chainId, module]."
+    constructor(chainId, module) {
+        if (!chainId || !module) throw "Must have [chainId, module]."
 
-    this.chainId = chainId
-    this.module = module
-  }
+        this.chainId = chainId
+        this.module = module
+    }
 
-  async updateAddressBaseModel(addressBaseItem) {
-    const url = `/address/appendOrUpdate`
+    async updateAddressBaseModel(addressBaseItem) {
+        const url = `/${this.module}/appendOrUpdate`
 
-    addressBaseItem.chainId = this.chainId
-    const rst = await http.post(url, addressBaseItem)
+        addressBaseItem.chainId = this.chainId
+        const rst = await http.post(url, addressBaseItem)
+
+        return rst.data
+    }
+
+
+    async find(pageNumber = 1, pageSize = 200) {
+        const url = `/${this.module}/findAddressListByNameNotNull`
+        const rst = await http.post(url, {
+            chainId: this.chainId,
+            pageNumber: pageNumber,
+            pageSize: pageSize,
+        })
+
+        return rst.data
+    }
 
-    return rst.data
-  }
 }

+ 12 - 5
src/plugins/model/Chain.js

@@ -1,10 +1,17 @@
 import http from 'axios'
 
 export default class EthMev {
-  static async getAll() {
-    const url = '/chain/getAll'
-    const rst = await http.post(url, {})
+    static async getAll(chainId,pageNumber = 1, pageSize = 200) {
+        // const url = '/chain/getAll'
+        const url = '/chain/findByChainIdAndPaginate'
+        const rst = await http.post(url, {
+            chainId: chainId,
+            pageNumber: pageNumber,
+            pageSize: pageSize,
+        })
+
+        return rst.data
+    }
+
 
-    return rst.data
-  }
 }

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

@@ -3,7 +3,8 @@ import http from 'axios'
 export default class TxModel {
     static MODULES = {
         HISTORY: 'history',
-        PENDING: 'pending'
+        PENDING: 'pending',
+        ADDRESS: 'address'
     }
 
     constructor(chainId, module) {