4
4
<transition name =" el-fade-in" >
5
5
<div v-if =" ipInfo.local && ipInfo.local.country && ipInfo.local.country.code == 'CN'" >
6
6
<el-tooltip class =" item" effect =" dark" :content =" ipInfo.local.ip" placement =" top" >
7
- <div @click = " copy (ipInfo.local.ip)" >
7
+ <div @click.stop = " onQuery (ipInfo.local.ip)" >
8
8
<el-tag style =" width : 50px ;" class =" ml-2" type =" success" >{{
9
9
ipInfo.layLocal?ipInfo.layLocal+"ms":"-ms" }}</el-tag >
10
10
<el-text style =" cursor : pointer ;margin-left : 5px ;white-space :nowrap ;vertical-align : -1px ;"
16
16
<transition name =" el-fade-in" >
17
17
<div v-if =" ipInfo.cloudflare && ipInfo.cloudflare.country && ipInfo.cloudflare.country.code != 'CN'" >
18
18
<el-tooltip class =" item" effect =" dark" :content =" ipInfo.cloudflare.ip" placement =" top" >
19
- <div @click = " copy (ipInfo.cloudflare.ip)" >
19
+ <div @click.stop = " onQuery (ipInfo.cloudflare.ip)" >
20
20
<el-tag style =" width : 50px ;" class =" ml-2" type =" success" >{{
21
21
ipInfo.layCloudflare?ipInfo.layCloudflare+"ms":"-ms" }}</el-tag >
22
22
<el-text style =" cursor : pointer ;margin-left : 5px ;white-space :nowrap ;vertical-align : -1px ;"
37
37
</transition >
38
38
</div >
39
39
</div >
40
+ <el-dialog align-center style =" width : 95vw ;max-width : 600px ;max-height : 85vh ;" v-model =" queryWindow" title =" IP查询" >
41
+ <el-input v-model =" ipInput" style =" max-width : 600px " placeholder =" 请输入IPV4/IPV6地址" clearable autocomplete =" new-password" >
42
+ <template #append ><el-button type =" primary" :icon =" Search" circle @click =" onQuery(ipInput)" /></template >
43
+ </el-input >
44
+ <table class =" ip-table" v-loading =" isQuerying" >
45
+ <tr >
46
+ <td @click =" copy(ipRet.ip)" >{{ ipRet.ip }}</td >
47
+ </tr >
48
+ <tr >
49
+ <td >{{ ipRet.addr }}</td >
50
+ </tr >
51
+ <tr v-if =" ipRet.as?.info || ipRet.type" >
52
+ <td >{{ ipRet.as?.info }} {{ ipRet.type }}</td >
53
+ </tr >
54
+ <tr v-if =" ipRet.as" >
55
+ <td >
56
+ ASN {{ ipRet.as?.number }}
57
+ </td >
58
+ </tr >
59
+ <tr v-if =" ipRet.country" >
60
+ <td >
61
+ {{ ipRet.country?.name }}({{ ipRet.country?.code }}) {{ ipRet.regions?.join(" ") }}
62
+ </td >
63
+ </tr >
64
+ <tr v-if =" ipRet.registered_country?.code != ipRet.country?.code" >
65
+ <td >
66
+ 注册地 {{ ipRet.registered_country?.name }}({{ ipRet.registered_country?.code }})
67
+ </td >
68
+ </tr >
69
+ <tr >
70
+ <td >{{ ipRet.as?.name }}</td >
71
+ </tr >
72
+ </table >
73
+ </el-dialog >
40
74
</template >
41
75
42
76
<script lang="ts" setup>
43
77
const props = defineProps ({
44
78
isVisible: Boolean
45
79
})
46
- import { reactive } from ' vue'
80
+ import { reactive , ref , type Ref } from ' vue'
47
81
import { ElMessage } from ' element-plus'
48
82
import { toClipboard } from ' @soerenmartius/vue3-clipboard'
83
+ import { Search } from ' @element-plus/icons-vue'
84
+ const queryWindow = ref (false )
85
+ const isQuerying = ref (false )
86
+ const ipInput = ref (" " )
87
+ const ipRet: Ref <any > = ref ({})
88
+ const onQuery = async (ip : string ) => {
89
+ queryWindow .value = true
90
+ try {
91
+ const ipv4 = / ^ ((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ / ;
92
+ const ipv6 = / ^ ([\d a-fA-F ] {1,4} :){6} ((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ | ^ ::([\d a-fA-F ] {1,4} :){0,4} ((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ | ^ ([\d a-fA-F ] {1,4} :):([\d a-fA-F ] {1,4} :){0,3} ((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ | ^ ([\d a-fA-F ] {1,4} :){2} :([\d a-fA-F ] {1,4} :){0,2} ((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ | ^ ([\d a-fA-F ] {1,4} :){3} :([\d a-fA-F ] {1,4} :){0,1} ((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ | ^ ([\d a-fA-F ] {1,4} :){4} :((25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )\. ){3} (25[0-5 ] | 2[0-4 ] \d | [01] ? \d\d ? )$ | ^ ([\d a-fA-F ] {1,4} :){7} [\d a-fA-F ] {1,4} $ | ^ :((:[\d a-fA-F ] {1,4} ){1,6} | :)$ | ^ [\d a-fA-F ] {1,4} :((:[\d a-fA-F ] {1,4} ){1,5} | :)$ | ^ ([\d a-fA-F ] {1,4} :){2} ((:[\d a-fA-F ] {1,4} ){1,4} | :)$ | ^ ([\d a-fA-F ] {1,4} :){3} ((:[\d a-fA-F ] {1,4} ){1,3} | :)$ | ^ ([\d a-fA-F ] {1,4} :){4} ((:[\d a-fA-F ] {1,4} ){1,2} | :)$ | ^ ([\d a-fA-F ] {1,4} :){5} :([\d a-fA-F ] {1,4} )? $ | ^ ([\d a-fA-F ] {1,4} :){6} :$ / ;
93
+
94
+ if (! ipv4 .test (ip ) && ! ipv6 .test (ip )) {
95
+ throw " 请输入正确的IP地址"
96
+ }
97
+ isQuerying .value = true
98
+ ipRet .value = await cachedQuery (ip )
99
+ isQuerying .value = false
100
+ } catch (error ) {
101
+ ElMessage .error (String (error ))
102
+ }
103
+ }
49
104
const ipInfo: {local: any , cloudflare: any ,layLocal: any ,layCloudflare: any } = reactive ({local:null , cloudflare:null ,layLocal:null ,layCloudflare:null })
50
105
const copy = (ip : string ) => {
51
106
toClipboard (ip )
@@ -70,13 +125,13 @@ let failure = false
70
125
async function cachedQuery(ip : string ) {
71
126
let ret = JSON .parse (localStorage .getItem (" cache_ip_" + ip ) || " {}" )
72
127
if (! ret .ip || new Date ().getTime () / 1000 - ret .time > 60 * 60 * 24 ){
73
- try {
74
- if (failure ) throw " "
75
- ret = await queryIp (ip )
76
- } catch (error ) {
77
- failure = true
78
- throw " 查询IP信息失败"
79
- }
128
+ try {
129
+ if (failure ) throw " "
130
+ ret = await queryIp (ip )
131
+ } catch (error ) {
132
+ failure = true
133
+ throw " 查询IP信息失败"
134
+ }
80
135
ret [' time' ] = new Date ().getTime () / 1000
81
136
localStorage .setItem (" cache_ip_" + ip , JSON .stringify (ret ))
82
137
}
@@ -163,6 +218,16 @@ watchCloudflare("ipv4.ip.sb")
163
218
padding : 2%
164
219
}
165
220
221
+ .ip-table {
222
+ height : 100% ;
223
+ margin : 20px auto ;
224
+ padding : 10px ;
225
+ border : #ffffff 1px solid ;
226
+ border-radius : 10px ;
227
+
228
+ text-align : center ;
229
+ }
230
+
166
231
@media (prefers-color-scheme: dark) {
167
232
.card {
168
233
background-color : rgb (18 , 18 , 18 );
0 commit comments