@peng_kai/kit 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/components/text/index.ts +13 -0
- package/admin/components/text/src/Amount.vue +114 -0
- package/admin/components/text/src/Datetime.vue +49 -0
- package/admin/components/text/src/Duration.vue +26 -0
- package/admin/components/text/src/Hash.vue +40 -0
- package/admin/components/text/src/createTagGetter.ts +13 -0
- package/admin/defines/definePage.ts +14 -0
- package/admin/defines/defineRoute.ts +161 -0
- package/admin/defines/defineRouteGuard.ts +56 -0
- package/admin/defines/defineStartup.ts +41 -0
- package/admin/defines/index.ts +4 -0
- package/antd/admin/FilterDrawer.vue +1 -0
- package/antd/admin/FilterReset.vue +2 -0
- package/antd/admin/index.ts +2 -1
- package/antd/admin/useFilterParams.ts +17 -0
- package/antd/components/InputNumberRange.vue +47 -0
- package/antd/index.ts +3 -1
- package/package.json +5 -3
- package/pnpm-lock.yaml +12 -0
- package/request/helpers.ts +0 -20
- package/request/index.ts +4 -0
- package/request/interceptors/checkCode.ts +3 -0
- package/request/interceptors/filterEmptyValue.ts +17 -0
- package/request/interceptors/formatPaging.ts +3 -0
- package/request/interceptors/index.ts +46 -1
- package/request/interceptors/popupMessage.ts +4 -0
- package/request/interceptors/returnResultType.ts +3 -0
- package/request/interceptors/unitizeAxiosError.ts +3 -0
- package/request/request.ts +6 -45
- package/utils/index.ts +72 -0
- package/vue/hooks/useIsMounted.ts +9 -0
- package/vue/index.ts +2 -1
- /package/{component → components}/infinite-query/index.ts +0 -0
- /package/{component → components}/infinite-query/src/InfiniteQuery.vue +0 -0
- /package/{component → components}/infinite-query/src/useCreateTrigger.ts +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Hash from './src/Hash.vue'
|
|
2
|
+
import Amount from './src/Amount.vue'
|
|
3
|
+
import Datetime from './src/Datetime.vue'
|
|
4
|
+
import Duration from './src/Duration.vue'
|
|
5
|
+
|
|
6
|
+
export { createTagGetter } from './src/createTagGetter'
|
|
7
|
+
|
|
8
|
+
export const Text = {
|
|
9
|
+
Hash,
|
|
10
|
+
Amount,
|
|
11
|
+
Datetime,
|
|
12
|
+
Duration,
|
|
13
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import bigNumber from 'bignumber.js'
|
|
4
|
+
import isNil from 'lodash-es/isNil'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 当 symbol 为以下值时,使用预设的 Logo
|
|
8
|
+
*/
|
|
9
|
+
const presetSymbols: Record<string, string> = {
|
|
10
|
+
USDT: 'https://api.iconify.design/cryptocurrency-color:usdt.svg',
|
|
11
|
+
TRX: 'https://api.iconify.design/cryptocurrency-color:trx.svg',
|
|
12
|
+
USDC: 'https://api.iconify.design/cryptocurrency-color:usdc.svg',
|
|
13
|
+
ETH: 'https://api.iconify.design/cryptocurrency-color:eth.svg',
|
|
14
|
+
BNB: 'https://api.iconify.design/cryptocurrency-color:bnb.svg',
|
|
15
|
+
BUSD: 'https://assets.coingecko.com/coins/images/9576/large/BUSD.png',
|
|
16
|
+
MATIC: 'https://api.iconify.design/cryptocurrency-color:matic.svg',
|
|
17
|
+
SOL: 'https://api.iconify.design/cryptocurrency-color:sol.svg',
|
|
18
|
+
}
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
const props = withDefaults(
|
|
23
|
+
defineProps<{
|
|
24
|
+
/** 金额 */
|
|
25
|
+
amount?: string | number
|
|
26
|
+
/** 符号Logo, 可以是币种Logo的URL、法定货币符号($) */
|
|
27
|
+
symbol?: string
|
|
28
|
+
/** 精度 */
|
|
29
|
+
precision?: number
|
|
30
|
+
/** 单位,如币种名称 */
|
|
31
|
+
unit?: string
|
|
32
|
+
/** 保留小数的位数 */
|
|
33
|
+
fractionDigits?: number
|
|
34
|
+
/** 是否填充 0 */
|
|
35
|
+
padZero?: boolean
|
|
36
|
+
/** 金额是否红/绿显示 */
|
|
37
|
+
colorful?: boolean
|
|
38
|
+
/** 是否是大约数 */
|
|
39
|
+
approx?: boolean
|
|
40
|
+
}>(),
|
|
41
|
+
{
|
|
42
|
+
padZero: false,
|
|
43
|
+
fractionDigits: 18,
|
|
44
|
+
colorful: false,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const amountText = computed(() => {
|
|
49
|
+
const _amount = props.amount
|
|
50
|
+
|
|
51
|
+
if (isNil(_amount))
|
|
52
|
+
return '-'
|
|
53
|
+
|
|
54
|
+
let bn = bigNumber(_amount)
|
|
55
|
+
bn = !isNil(props.precision) ? bn.dividedBy(10 ** props.precision) : bn
|
|
56
|
+
let bnt = bn.toFormat(props.fractionDigits)
|
|
57
|
+
bnt = props.padZero ? bnt : bnt.replace(/\.?0+$/, '')
|
|
58
|
+
|
|
59
|
+
return bnt
|
|
60
|
+
})
|
|
61
|
+
const amountColor = computed(() => {
|
|
62
|
+
const num = Number.parseFloat(props.amount as string)
|
|
63
|
+
|
|
64
|
+
if (!props.colorful || (Number.isNaN(num) ? true : num === 0))
|
|
65
|
+
return ''
|
|
66
|
+
|
|
67
|
+
return num > 0 ? '#52c41a' : (num < 0 ? '#ff4d4f' : '#000')
|
|
68
|
+
})
|
|
69
|
+
const symbol = computed(() => presetSymbols[props.symbol!] ?? props.symbol ?? '')
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<div class="amount-wrapper" :style="{ '--amount-color': amountColor }">
|
|
74
|
+
<!-- 约等于 -->
|
|
75
|
+
<span v-if="props.approx" class="color-$amount-color">≈</span>
|
|
76
|
+
|
|
77
|
+
<!-- 符号 -->
|
|
78
|
+
<img v-if="symbol.startsWith('http')" class="symbol-logo" :src="symbol" :alt="props.unit"> <!-- 图片Logo -->
|
|
79
|
+
<span v-else class="color-$amount-color">{{ symbol }}</span> <!-- 文本Logo,如法定币种符号(¥、$) -->
|
|
80
|
+
|
|
81
|
+
<!-- 金额 -->
|
|
82
|
+
<span class="color-$amount-color amount">{{ amountText }}</span>
|
|
83
|
+
|
|
84
|
+
<!-- 单位 -->
|
|
85
|
+
<span v-if="props.unit" class="unit">{{ props.unit }}</span>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<style lang="scss">
|
|
90
|
+
.amount-wrapper {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
|
|
94
|
+
.symbol-logo {
|
|
95
|
+
display: block;
|
|
96
|
+
width: 1.1em;
|
|
97
|
+
height: 1.1em;
|
|
98
|
+
margin-right: 0.2em;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.amount {
|
|
102
|
+
font-family: 'dinm';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.currency-name {
|
|
106
|
+
margin-left: 0.2em;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.unit {
|
|
110
|
+
display: inline-block;
|
|
111
|
+
margin-left: 0.2em;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Tooltip as ATooltip } from "ant-design-vue";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import dayjs from 'dayjs'
|
|
5
|
+
import { default as relativeTime } from 'dayjs/plugin/relativeTime'
|
|
6
|
+
import 'dayjs/locale/zh'
|
|
7
|
+
|
|
8
|
+
dayjs.extend(relativeTime)
|
|
9
|
+
dayjs.locale('zh')
|
|
10
|
+
|
|
11
|
+
defineOptions({
|
|
12
|
+
inheritAttrs: false,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
timestamp?: number | string
|
|
18
|
+
template?: string
|
|
19
|
+
}>(),
|
|
20
|
+
{
|
|
21
|
+
template: 'MM-DD HH:mm:ss',
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const timestamp = computed(() => {
|
|
26
|
+
let tsStr = String(props.timestamp)
|
|
27
|
+
|
|
28
|
+
if (tsStr.length === 10)
|
|
29
|
+
tsStr += '000'
|
|
30
|
+
if (tsStr.length !== 13)
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
const tsNum = Number.parseInt(tsStr)
|
|
34
|
+
|
|
35
|
+
return Number.isNaN(tsNum) ? undefined : tsNum
|
|
36
|
+
})
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<ATooltip destroyTooltipOnHide>
|
|
41
|
+
<template v-if="timestamp" #title>
|
|
42
|
+
<div>{{ dayjs(timestamp).fromNow() }}</div>
|
|
43
|
+
<div>{{ dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
|
44
|
+
</template>
|
|
45
|
+
<span v-bind="$attrs">{{ timestamp ? dayjs(timestamp).format(props.template) : '-' }}</span>
|
|
46
|
+
</ATooltip>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<style scoped lang="scss"></style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
seconds: number
|
|
6
|
+
}>()
|
|
7
|
+
|
|
8
|
+
const formattedDuration = computed(() => {
|
|
9
|
+
const days = Math.floor(props.seconds / (3600 * 24))
|
|
10
|
+
const hours = Math.floor((props.seconds % (3600 * 24)) / 3600)
|
|
11
|
+
const minutes = Math.floor((props.seconds % 3600) / 60)
|
|
12
|
+
const seconds = props.seconds % 60
|
|
13
|
+
let formattedDuration = ''
|
|
14
|
+
|
|
15
|
+
days >= 1 && (formattedDuration += `${Math.floor(days)}天 `)
|
|
16
|
+
hours >= 1 && (formattedDuration += `${Math.floor(hours)}小时 `)
|
|
17
|
+
minutes >= 1 && (formattedDuration += `${Math.floor(minutes)}分钟 `)
|
|
18
|
+
seconds >= 1 && (formattedDuration += `${Math.floor(seconds)}秒`)
|
|
19
|
+
|
|
20
|
+
return formattedDuration
|
|
21
|
+
})
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<template>
|
|
25
|
+
<span>{{ formattedDuration }}</span>
|
|
26
|
+
</template>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Tooltip as ATooltip, TypographyLink as ATypographyLink } from "ant-design-vue";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import { desensitize, getScanBrowser } from '../../../../utils'
|
|
5
|
+
|
|
6
|
+
type HashType = Parameters<typeof getScanBrowser>[2]
|
|
7
|
+
|
|
8
|
+
defineOptions({
|
|
9
|
+
inheritAttrs: false,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(
|
|
13
|
+
defineProps<{
|
|
14
|
+
hash: string
|
|
15
|
+
hide?: boolean
|
|
16
|
+
chain?: string
|
|
17
|
+
type?: HashType
|
|
18
|
+
}>(),
|
|
19
|
+
{
|
|
20
|
+
hide: true,
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const href = computed(() => {
|
|
25
|
+
const { hash, chain, type } = props
|
|
26
|
+
|
|
27
|
+
if (!hash || !chain || !type)
|
|
28
|
+
return
|
|
29
|
+
return getScanBrowser(chain, hash, type)
|
|
30
|
+
})
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<ATypographyLink :href="href" :copyable="{ text: props.hash, tooltip: false }" target="_blank">
|
|
35
|
+
<ATooltip>
|
|
36
|
+
<template v-if="props.hide" #title>{{ props.hash }}</template>
|
|
37
|
+
<span v-bind="$attrs">{{ props.hide ? desensitize(props.hash) : props.hash }}</span>
|
|
38
|
+
</ATooltip>
|
|
39
|
+
</ATypographyLink>
|
|
40
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { computed, h } from "vue";
|
|
2
|
+
import { Tag as ATag } from 'ant-design-vue'
|
|
3
|
+
import type { TagProps } from 'ant-design-vue'
|
|
4
|
+
|
|
5
|
+
export function createTagGetter(typeMapFn: () => { [code: number]: [ text: string, color: TagProps['color'] ] }) {
|
|
6
|
+
const typeMap = computed(typeMapFn)
|
|
7
|
+
|
|
8
|
+
return (type: number) => {
|
|
9
|
+
const [text, color] = typeMap.value[type] ?? []
|
|
10
|
+
|
|
11
|
+
return text ? h(ATag, { color }, () => text) : h('span', null, '-')
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineComponent, defineAsyncComponent, h } from 'vue'
|
|
2
|
+
import type { AsyncComponentLoader } from 'vue'
|
|
3
|
+
|
|
4
|
+
export { definePage }
|
|
5
|
+
|
|
6
|
+
function definePage(loader: AsyncComponentLoader) {
|
|
7
|
+
return defineComponent({
|
|
8
|
+
setup() {
|
|
9
|
+
const Page = defineAsyncComponent(loader)
|
|
10
|
+
|
|
11
|
+
return () => h(Page, null)
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { RouteRecordRaw } from 'vue-router'
|
|
2
|
+
import merge from 'lodash-es/merge'
|
|
3
|
+
import type { VNode } from 'vue'
|
|
4
|
+
import { definePage } from './definePage'
|
|
5
|
+
import { ENV } from "../../utils";
|
|
6
|
+
|
|
7
|
+
export { defineRoute, getRoutes }
|
|
8
|
+
|
|
9
|
+
const RouteSymbol = Symbol('app-route')
|
|
10
|
+
|
|
11
|
+
/** 定义路由 */
|
|
12
|
+
function defineRoute(route: (params: { definePage: typeof definePage }) => RouteRecordRaw[]) {
|
|
13
|
+
const routeFn: any = () => route({ definePage })
|
|
14
|
+
routeFn[RouteSymbol] = true
|
|
15
|
+
|
|
16
|
+
return routeFn
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 获取路由 */
|
|
20
|
+
async function getRoutes() {
|
|
21
|
+
const routeFileRE = /\/([A-Za-z0-9-]+.)?route\.ts$/
|
|
22
|
+
const routeFiles = Object.fromEntries(
|
|
23
|
+
Object.entries(getRoutes.modules).filter(([n]) => routeFileRE.test(n)),
|
|
24
|
+
) as Record<string, Function>
|
|
25
|
+
let routes: RouteRecordRaw[] = []
|
|
26
|
+
|
|
27
|
+
for (const name in routeFiles) {
|
|
28
|
+
const module: any = await routeFiles[name]()
|
|
29
|
+
|
|
30
|
+
if (Object.hasOwn(module?.default ?? {}, RouteSymbol))
|
|
31
|
+
Array.prototype.push.apply(routes, module.default())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 处理路由
|
|
35
|
+
routes = meregeDefaultRouteParams(routes)
|
|
36
|
+
routes = sortRoute(routes)
|
|
37
|
+
|
|
38
|
+
// 输出路由
|
|
39
|
+
if (!ENV.isProd) {
|
|
40
|
+
console.groupCollapsed('一级路由')
|
|
41
|
+
|
|
42
|
+
console.table(
|
|
43
|
+
routes.map(route => ({ order: route.meta?.order, ...route })),
|
|
44
|
+
['order', 'name', 'path'],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
console.groupEnd()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return routes
|
|
51
|
+
}
|
|
52
|
+
getRoutes.modules = {} as any
|
|
53
|
+
|
|
54
|
+
/** 给路由填充默认参数 */
|
|
55
|
+
const defaultRouteParams: Partial<RouteRecordRaw> = {
|
|
56
|
+
meta: {
|
|
57
|
+
title: '标题',
|
|
58
|
+
icon: undefined,
|
|
59
|
+
order: 10,
|
|
60
|
+
requireAuth: true,
|
|
61
|
+
keepAlive: false,
|
|
62
|
+
hiddenTab: false,
|
|
63
|
+
pageKeyFn: route => route.path,
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function meregeDefaultRouteParams(routes: RouteRecordRaw[], parentRoute?: RouteRecordRaw) {
|
|
68
|
+
const _routes: typeof routes = []
|
|
69
|
+
|
|
70
|
+
for (const route of routes) {
|
|
71
|
+
if (route.children?.length)
|
|
72
|
+
route.children = meregeDefaultRouteParams(route.children, route)
|
|
73
|
+
|
|
74
|
+
const newRoute = merge({}, defaultRouteParams, route)
|
|
75
|
+
const { menuOrder } = newRoute.meta!
|
|
76
|
+
|
|
77
|
+
// 处理 menuOrder
|
|
78
|
+
if (typeof menuOrder === 'string')
|
|
79
|
+
newRoute.meta!.menuOrder = menuOrder.replace('..', (parentRoute?.name ?? '') as string)
|
|
80
|
+
|
|
81
|
+
_routes.push(newRoute)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return _routes
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 给路由排序 */
|
|
88
|
+
function sortRoute(routes: RouteRecordRaw[]) {
|
|
89
|
+
const _routes = routes.sort((r1, r2) => r1!.meta!.order! - r2!.meta!.order!)
|
|
90
|
+
|
|
91
|
+
for (const route of _routes) {
|
|
92
|
+
if (route.children?.length)
|
|
93
|
+
route.children = sortRoute(route.children)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return _routes
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare module 'vue-router' {
|
|
100
|
+
interface RouteMeta {
|
|
101
|
+
/**
|
|
102
|
+
* 路由标题
|
|
103
|
+
*
|
|
104
|
+
* @description 可用来作 document.title 和菜单的名称
|
|
105
|
+
*
|
|
106
|
+
* 默认:APP 名称
|
|
107
|
+
*/
|
|
108
|
+
title?: string | (() => string)
|
|
109
|
+
/**
|
|
110
|
+
* 菜单和面包屑对应的图标
|
|
111
|
+
*
|
|
112
|
+
* 默认:`''`
|
|
113
|
+
*/
|
|
114
|
+
icon?: (() => VNode)
|
|
115
|
+
/**
|
|
116
|
+
* 路由添加顺序,仅作用于一级路由
|
|
117
|
+
*
|
|
118
|
+
* 默认:`10`
|
|
119
|
+
*/
|
|
120
|
+
order?: number
|
|
121
|
+
/**
|
|
122
|
+
* 是否需要登录
|
|
123
|
+
*
|
|
124
|
+
* 默认:`false`
|
|
125
|
+
*/
|
|
126
|
+
requireAuth?: boolean
|
|
127
|
+
/**
|
|
128
|
+
* 缓存页面
|
|
129
|
+
*
|
|
130
|
+
* 默认:`true`
|
|
131
|
+
*/
|
|
132
|
+
keepAlive?: boolean
|
|
133
|
+
/**
|
|
134
|
+
* 菜单排序(升序)
|
|
135
|
+
*
|
|
136
|
+
* 格式:[parent]@[order]
|
|
137
|
+
* - `parent`:可选。不填则默认取路由的 name 值,并在作为一级菜单显示
|
|
138
|
+
* - `order`:必选。路由排序,值越小排越前
|
|
139
|
+
*
|
|
140
|
+
* 例子:
|
|
141
|
+
* - `@10`:一级菜单
|
|
142
|
+
* - `..@10`:所在的路由层级的父级菜单,例如父级菜单是 admin,那么`..`就是`admin`
|
|
143
|
+
* - `admin@10`:指定菜单的父级为 `admin`
|
|
144
|
+
*
|
|
145
|
+
* 默认:undefined。不在菜单中显示
|
|
146
|
+
*/
|
|
147
|
+
menuOrder?: string
|
|
148
|
+
/**
|
|
149
|
+
* 是否隐藏标签页
|
|
150
|
+
*
|
|
151
|
+
* 默认:`false`
|
|
152
|
+
*/
|
|
153
|
+
hiddenTab?: boolean
|
|
154
|
+
/**
|
|
155
|
+
* 用于生成页面运行时的 key
|
|
156
|
+
*
|
|
157
|
+
* 默认:取 route 对象中 path 值
|
|
158
|
+
*/
|
|
159
|
+
pageKeyFn?: (route: RouteLocationNormalizedLoaded) => string
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Router } from 'vue-router'
|
|
2
|
+
import { ENV } from "../../utils";
|
|
3
|
+
|
|
4
|
+
export { defineRouteGuard, getRouteGuards }
|
|
5
|
+
|
|
6
|
+
interface IGuardConfig {
|
|
7
|
+
/** 路由守卫添加顺序(升序加载) */
|
|
8
|
+
order?: number
|
|
9
|
+
setup: (router: Router) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 路由守卫标识
|
|
13
|
+
const RouteGuardSymbol = Symbol('app-route-guard')
|
|
14
|
+
|
|
15
|
+
// 定义路由守卫
|
|
16
|
+
function defineRouteGuard(guard: IGuardConfig) {
|
|
17
|
+
(guard as any)[RouteGuardSymbol] = true
|
|
18
|
+
guard.order ??= 10
|
|
19
|
+
|
|
20
|
+
return guard
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 获取路由守卫
|
|
24
|
+
async function getRouteGuards() {
|
|
25
|
+
const routeGuardFileRE = /\/([A-Za-z0-9-]+.)?route-guard\.ts$/
|
|
26
|
+
const routeGuardFiles = Object.fromEntries(
|
|
27
|
+
Object.entries(getRouteGuards.modules).filter(([n]) => routeGuardFileRE.test(n)),
|
|
28
|
+
) as Record<string, Function>
|
|
29
|
+
let routeGuards: Array<IGuardConfig> = []
|
|
30
|
+
let routeGuardRecord: { file: string; order: number }[] = []
|
|
31
|
+
|
|
32
|
+
for (const name in routeGuardFiles) {
|
|
33
|
+
const module: any = await routeGuardFiles[name]()
|
|
34
|
+
|
|
35
|
+
if (Object.hasOwn(module?.default ?? {}, RouteGuardSymbol)) {
|
|
36
|
+
const guard = module.default
|
|
37
|
+
|
|
38
|
+
routeGuards.push(guard)
|
|
39
|
+
routeGuardRecord.push({ file: name, order: guard.order })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 处理路由守卫
|
|
44
|
+
routeGuards = routeGuards.sort()
|
|
45
|
+
routeGuardRecord = routeGuardRecord.sort()
|
|
46
|
+
|
|
47
|
+
// 输出路由守卫
|
|
48
|
+
if (!ENV.isProd) {
|
|
49
|
+
console.groupCollapsed('路由守卫')
|
|
50
|
+
console.table(routeGuardRecord, ['order', 'file'])
|
|
51
|
+
console.groupEnd()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return routeGuards
|
|
55
|
+
}
|
|
56
|
+
getRouteGuards.modules = {} as any
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { App } from 'vue'
|
|
2
|
+
import { ENV } from "../../utils";
|
|
3
|
+
|
|
4
|
+
export { defineStartup, getStartups }
|
|
5
|
+
|
|
6
|
+
type StartupFn = (app: App) => void | Promise<void>
|
|
7
|
+
|
|
8
|
+
const StartupSymbol = Symbol('app-startup')
|
|
9
|
+
|
|
10
|
+
function defineStartup(startup: StartupFn) {
|
|
11
|
+
(startup as any)[StartupSymbol] = true
|
|
12
|
+
|
|
13
|
+
return startup
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function getStartups() {
|
|
17
|
+
const moduleLoaders = getStartups.modules as Record<string, Function>
|
|
18
|
+
const startupPaths: string[] = []
|
|
19
|
+
const startups: Array<StartupFn> = []
|
|
20
|
+
|
|
21
|
+
for (const [path, moduleLoader] of Object.entries(moduleLoaders)) {
|
|
22
|
+
const module: any = await moduleLoader()
|
|
23
|
+
const plugin: StartupFn = module?.default ?? {}
|
|
24
|
+
|
|
25
|
+
if (!Object.hasOwn(plugin, StartupSymbol))
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
startupPaths.push(path)
|
|
29
|
+
startups.push(plugin)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 输出 App 插件
|
|
33
|
+
if (!ENV.isProd) {
|
|
34
|
+
console.groupCollapsed('启动时')
|
|
35
|
+
console.table(startupPaths.map(path => ({ path })), ['path'])
|
|
36
|
+
console.groupEnd()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return startups
|
|
40
|
+
}
|
|
41
|
+
getStartups.modules = {} as any
|
package/antd/admin/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { default as FilterDrawer } from './FilterDrawer.vue'
|
|
2
2
|
export { default as FilterReset } from './FilterReset.vue'
|
|
3
|
-
export { default as FilterParam, paramTypes} from './FilterParam.vue'
|
|
3
|
+
export { default as FilterParam, paramTypes} from './FilterParam.vue'
|
|
4
|
+
export { useFilterParams } from './useFilterParams'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { extendRef } from '@vueuse/core'
|
|
2
|
+
import { reactive } from "vue";
|
|
3
|
+
|
|
4
|
+
const defaultPageParams: any = { page: 1, page_size: 10 }
|
|
5
|
+
|
|
6
|
+
export function useFilterParams<R extends Api.Request, AP extends Api.GetParam<R>, BP extends AP>(
|
|
7
|
+
_api: R,
|
|
8
|
+
buildParams: () => BP,
|
|
9
|
+
pageParams: Pick<AP, 'page' | 'page_size'> = defaultPageParams,
|
|
10
|
+
) {
|
|
11
|
+
const params = reactive({ ...pageParams, ...buildParams() } as AP & BP)
|
|
12
|
+
const update = (newParams?: Partial<AP>) => {
|
|
13
|
+
Object.assign(params, { ...buildParams(), ...newParams })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return extendRef(params, { update })
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { Form, InputNumber as AInputNumber } from 'ant-design-vue'
|
|
4
|
+
|
|
5
|
+
const props = withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
value: [number, number]
|
|
8
|
+
min?: number
|
|
9
|
+
max?: number
|
|
10
|
+
}>(),
|
|
11
|
+
{
|
|
12
|
+
min: Number.NEGATIVE_INFINITY,
|
|
13
|
+
max: Number.POSITIVE_INFINITY,
|
|
14
|
+
},
|
|
15
|
+
)
|
|
16
|
+
const emits = defineEmits<{
|
|
17
|
+
(e: 'update:value', value: [number, number]): void
|
|
18
|
+
}>()
|
|
19
|
+
|
|
20
|
+
const formItemContext = Form.useInjectFormItemContext()
|
|
21
|
+
const minValue = computed({
|
|
22
|
+
get() {
|
|
23
|
+
return props.value[0]
|
|
24
|
+
},
|
|
25
|
+
set(value) {
|
|
26
|
+
emits('update:value', [value, props.value[1]])
|
|
27
|
+
formItemContext.onFieldChange()
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
const maxValue = computed({
|
|
31
|
+
get() {
|
|
32
|
+
return props.value[1]
|
|
33
|
+
},
|
|
34
|
+
set(value) {
|
|
35
|
+
emits('update:value', [props.value[0], value])
|
|
36
|
+
formItemContext.onFieldChange()
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="flex items-center">
|
|
43
|
+
<AInputNumber v-model:value="minValue" class="w-full" :min="props.min" :max="props.max" />
|
|
44
|
+
<span> ~ </span>
|
|
45
|
+
<AInputNumber v-model:value="maxValue" class="w-full" :min="props.min" :max="props.max" />
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
package/antd/index.ts
CHANGED
|
@@ -2,4 +2,6 @@ export { useAntdModal } from './hooks/useAntdModal'
|
|
|
2
2
|
export { useAntdDrawer } from './hooks/useAntdDrawer'
|
|
3
3
|
export { useAntdTable } from './hooks/useAntdTable'
|
|
4
4
|
export { useAntdForm } from './hooks/useAntdForm'
|
|
5
|
-
export { createAntdModal } from './hooks/createAntdModal'
|
|
5
|
+
export { createAntdModal } from './hooks/createAntdModal'
|
|
6
|
+
export { default as InputNumberRange } from './components/InputNumberRange.vue'
|
|
7
|
+
export type { TField } from './hooks/useAntdForm.ts'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peng_kai/kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"bignumber.js": "^9.1.2",
|
|
18
18
|
"dayjs": "^1.11.10",
|
|
19
19
|
"lodash-es": "^4.17.21",
|
|
20
|
-
"vue": "^3.3.7"
|
|
20
|
+
"vue": "^3.3.7",
|
|
21
|
+
"vue-router": "^4.2.5"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@types/lodash-es": "^4.17.10",
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"bignumber.js": "9.x",
|
|
34
35
|
"dayjs": "1.x",
|
|
35
36
|
"lodash-es": "4.x",
|
|
36
|
-
"vue": "3.3.x"
|
|
37
|
+
"vue": "3.3.x",
|
|
38
|
+
"vue-router": "4.2.x"
|
|
37
39
|
}
|
|
38
40
|
}
|
package/pnpm-lock.yaml
CHANGED
|
@@ -29,6 +29,9 @@ dependencies:
|
|
|
29
29
|
vue:
|
|
30
30
|
specifier: ^3.3.7
|
|
31
31
|
version: 3.3.7
|
|
32
|
+
vue-router:
|
|
33
|
+
specifier: ^4.2.5
|
|
34
|
+
version: 4.2.5(vue@3.3.7)
|
|
32
35
|
|
|
33
36
|
devDependencies:
|
|
34
37
|
'@types/lodash-es':
|
|
@@ -554,6 +557,15 @@ packages:
|
|
|
554
557
|
vue: 3.3.7
|
|
555
558
|
dev: false
|
|
556
559
|
|
|
560
|
+
/vue-router@4.2.5(vue@3.3.7):
|
|
561
|
+
resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==}
|
|
562
|
+
peerDependencies:
|
|
563
|
+
vue: ^3.2.0
|
|
564
|
+
dependencies:
|
|
565
|
+
'@vue/devtools-api': 6.5.1
|
|
566
|
+
vue: 3.3.7
|
|
567
|
+
dev: false
|
|
568
|
+
|
|
557
569
|
/vue-types@3.0.2(vue@3.3.7):
|
|
558
570
|
resolution: {integrity: sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==}
|
|
559
571
|
engines: {node: '>=10.15.0'}
|
package/request/helpers.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { AxiosInstance } from 'axios'
|
|
2
|
-
|
|
3
1
|
export { isTimeout, ApiCode, ApiError }
|
|
4
2
|
|
|
5
3
|
enum ApiCode {
|
|
@@ -12,24 +10,6 @@ function isTimeout(error: any) {
|
|
|
12
10
|
return error?.message?.toLowerCase().includes('timeout')
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
// function useInterceptor<Obj extends { use: (...args: any) => any }, Args extends Obj>(obj: Obj) {}
|
|
16
|
-
|
|
17
|
-
/** 获取 services 目录下的所以接口服务 */
|
|
18
|
-
// function getServices() {
|
|
19
|
-
// const serversModule = import.meta.glob('./services/**[!.].ts', { eager: true })
|
|
20
|
-
// const servers: Record<string, { server: AxiosInstance } | undefined> = {}
|
|
21
|
-
|
|
22
|
-
// for (const [key, module] of Object.entries(serversModule)) {
|
|
23
|
-
// const name = key.match(/\/([0-9a-zA-Z]+)\.ts$/)?.[1]
|
|
24
|
-
|
|
25
|
-
// if (name)
|
|
26
|
-
// servers[`${name}.api`] = module as any
|
|
27
|
-
// }
|
|
28
|
-
|
|
29
|
-
// return servers
|
|
30
|
-
// }
|
|
31
|
-
|
|
32
|
-
// 命名原因:避免由于自动导入造成的命名冲突
|
|
33
13
|
class ApiError<TResp = any> extends Error {
|
|
34
14
|
public static is<ErrorResp = any>(error: any): error is ApiError<ErrorResp> {
|
|
35
15
|
return !!error?.isApiError
|
package/request/index.ts
CHANGED
|
@@ -2,6 +2,9 @@ import axios from "axios";
|
|
|
2
2
|
import type { AxiosInterceptorManager } from "axios";
|
|
3
3
|
import { ApiCode} from "../helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* 【响应拦截器】检查 API 的 code 字段
|
|
7
|
+
*/
|
|
5
8
|
export function checkCode(): Parameters<AxiosInterceptorManager<any>['use']> {
|
|
6
9
|
return [
|
|
7
10
|
(resp) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AxiosInterceptorManager } from "axios";
|
|
2
|
+
import pickBy from 'lodash-es/pickBy'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 【请求拦截器】过滤空值,如:null、undefined、''、NaN
|
|
6
|
+
*/
|
|
7
|
+
export function filterEmptyValue(): Parameters<AxiosInterceptorManager<any>['use']> {
|
|
8
|
+
return [
|
|
9
|
+
(req) => {
|
|
10
|
+
req.params = pickBy(req.params, (v) => {
|
|
11
|
+
return v !== null && v !== undefined && v !== '' && !Number.isNaN(v)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
return req
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -2,4 +2,49 @@ export { popupMessage } from './popupMessage'
|
|
|
2
2
|
export { returnResultType } from './returnResultType'
|
|
3
3
|
export { unitizeAxiosError } from './unitizeAxiosError'
|
|
4
4
|
export { checkCode } from './checkCode'
|
|
5
|
-
export { formatPaging } from './formatPaging'
|
|
5
|
+
export { formatPaging } from './formatPaging'
|
|
6
|
+
export { filterEmptyValue } from './filterEmptyValue'
|
|
7
|
+
|
|
8
|
+
declare module 'axios' {
|
|
9
|
+
interface AxiosRequestConfig {
|
|
10
|
+
/**
|
|
11
|
+
* 接口返回的数据类型
|
|
12
|
+
*
|
|
13
|
+
* - `axios`:Axios 的 Response
|
|
14
|
+
* - `api`:API 数据完整结构
|
|
15
|
+
* - `data`:API 数据中的 data 字段值,**默认**
|
|
16
|
+
*/
|
|
17
|
+
resultType?: 'axios' | 'api' | 'data'
|
|
18
|
+
/**
|
|
19
|
+
* 是否加入查询参数时间戳
|
|
20
|
+
*
|
|
21
|
+
* - `true`:**默认**
|
|
22
|
+
*/
|
|
23
|
+
joinTime?: boolean
|
|
24
|
+
/**
|
|
25
|
+
* 当接口报错时,是否弹出 message 提示。取值:
|
|
26
|
+
* - `false`:弹出 message 提示
|
|
27
|
+
* - `true`:弹出 message 提示,内容使用接口 msg 的值,**默认**
|
|
28
|
+
* - string:自定义 message 内容
|
|
29
|
+
*/
|
|
30
|
+
errorMessage?: string | boolean
|
|
31
|
+
/**
|
|
32
|
+
* 当接口成功时,是否弹出 message 提示。取值:
|
|
33
|
+
* - `false`:弹出 message 提示,**默认**
|
|
34
|
+
* - `true`:弹出 message 提示,内容使用接口 msg 的值
|
|
35
|
+
* - string:自定义 message 内容
|
|
36
|
+
*/
|
|
37
|
+
successMessage?: string | boolean
|
|
38
|
+
/**
|
|
39
|
+
* 是否检查 API 的 code 字段
|
|
40
|
+
*
|
|
41
|
+
* - `true`:当 code 非 0 时报错,**默认**
|
|
42
|
+
* - `false`:不检查
|
|
43
|
+
*/
|
|
44
|
+
checkCode?: boolean
|
|
45
|
+
/**
|
|
46
|
+
* 需要忽略的 code,即数组中的 code 都视为请求成功
|
|
47
|
+
*/
|
|
48
|
+
ignoreCode?: number[]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -2,6 +2,10 @@ import axios from 'axios'
|
|
|
2
2
|
import type { AxiosInterceptorManager } from "axios";
|
|
3
3
|
import { isTimeout } from "../helpers";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* 【响应拦截器】弹出 message 提示
|
|
7
|
+
* @param popup 弹出提示的函数
|
|
8
|
+
*/
|
|
5
9
|
export function popupMessage(popup: (type: 'error' | 'success', message: string) => void): Parameters<AxiosInterceptorManager<any>['use']> {
|
|
6
10
|
return [
|
|
7
11
|
(resp) => {
|
package/request/request.ts
CHANGED
|
@@ -8,6 +8,7 @@ function createRequest<Req, OResp, Resp = Api.TransformPageResult<OResp>>(
|
|
|
8
8
|
paramBuilder: (params: any) => any,
|
|
9
9
|
) {
|
|
10
10
|
type ReqConfig = Partial<AxiosRequestConfig>
|
|
11
|
+
let defaultConfig: ReqConfig | null = null
|
|
11
12
|
|
|
12
13
|
// 返回 Axios 的 Response
|
|
13
14
|
async function request<Rt extends 'axios'>(reqData: Req, options: ReqConfig & { resultType: Rt }): Promise<AxiosResponse<Resp>>
|
|
@@ -28,13 +29,17 @@ function createRequest<Req, OResp, Resp = Api.TransformPageResult<OResp>>(
|
|
|
28
29
|
const res = await service.request({
|
|
29
30
|
apiName: `${serviceName}/${id}`,
|
|
30
31
|
...params,
|
|
31
|
-
...config,
|
|
32
|
+
...Object.assign({}, defaultConfig, config),
|
|
32
33
|
})
|
|
33
34
|
|
|
34
35
|
return res as Api.GetDataField<Resp>
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
request.id = id
|
|
39
|
+
request.setDefaultConfig = (config: ReqConfig) => {
|
|
40
|
+
defaultConfig = config
|
|
41
|
+
return request
|
|
42
|
+
}
|
|
38
43
|
|
|
39
44
|
return request
|
|
40
45
|
}
|
|
@@ -43,47 +48,3 @@ function createRequest<Req, OResp, Resp = Api.TransformPageResult<OResp>>(
|
|
|
43
48
|
createRequest.services = {} as Record<string, {
|
|
44
49
|
server: AxiosInstance;
|
|
45
50
|
} | undefined>
|
|
46
|
-
|
|
47
|
-
declare module 'axios' {
|
|
48
|
-
interface AxiosRequestConfig {
|
|
49
|
-
/**
|
|
50
|
-
* 接口返回的数据类型
|
|
51
|
-
*
|
|
52
|
-
* - `axios`:Axios 的 Response
|
|
53
|
-
* - `api`:API 数据完整结构
|
|
54
|
-
* - `data`:API 数据中的 data 字段值,**默认**
|
|
55
|
-
*/
|
|
56
|
-
resultType?: 'axios' | 'api' | 'data'
|
|
57
|
-
/**
|
|
58
|
-
* 是否加入查询参数时间戳
|
|
59
|
-
*
|
|
60
|
-
* - `true`:**默认**
|
|
61
|
-
*/
|
|
62
|
-
joinTime?: boolean
|
|
63
|
-
/**
|
|
64
|
-
* 当接口报错时,是否弹出 message 提示。取值:
|
|
65
|
-
* - `false`:弹出 message 提示
|
|
66
|
-
* - `true`:弹出 message 提示,内容使用接口 msg 的值,**默认**
|
|
67
|
-
* - string:自定义 message 内容
|
|
68
|
-
*/
|
|
69
|
-
errorMessage?: string | boolean
|
|
70
|
-
/**
|
|
71
|
-
* 当接口成功时,是否弹出 message 提示。取值:
|
|
72
|
-
* - `false`:弹出 message 提示,**默认**
|
|
73
|
-
* - `true`:弹出 message 提示,内容使用接口 msg 的值
|
|
74
|
-
* - string:自定义 message 内容
|
|
75
|
-
*/
|
|
76
|
-
successMessage?: string | boolean
|
|
77
|
-
/**
|
|
78
|
-
* 是否检查 API 的 code 字段
|
|
79
|
-
*
|
|
80
|
-
* - `true`:当 code 非 0 时报错,**默认**
|
|
81
|
-
* - `false`:不检查
|
|
82
|
-
*/
|
|
83
|
-
checkCode?: boolean
|
|
84
|
-
/**
|
|
85
|
-
* 需要忽略的 code,即数组中的 code 都视为请求成功
|
|
86
|
-
*/
|
|
87
|
-
ignoreCode?: number[]
|
|
88
|
-
}
|
|
89
|
-
}
|
package/utils/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const ENV = {
|
|
2
|
+
get isDev() {
|
|
3
|
+
return (import.meta as any).env.DEV
|
|
4
|
+
},
|
|
5
|
+
get isProd() {
|
|
6
|
+
return (import.meta as any).env.PROD
|
|
7
|
+
},
|
|
8
|
+
get isStaging() {
|
|
9
|
+
return (import.meta as any).env.MODE === 'staging'
|
|
10
|
+
},
|
|
11
|
+
get isTest() {
|
|
12
|
+
return this.isDev || this.isStaging
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 睡眠
|
|
18
|
+
* @param dur 时长
|
|
19
|
+
*/
|
|
20
|
+
export function sleep(dur: number) {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
setTimeout(resolve, dur)
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 字符串脱敏,省略中间字符串
|
|
28
|
+
* @param str 字符串
|
|
29
|
+
* @param startChars 开头显示字符数量
|
|
30
|
+
* @param endChars 结尾显示字符数量
|
|
31
|
+
*/
|
|
32
|
+
export function desensitize(str: string | undefined | null, startChars = 4, endChars?: number) {
|
|
33
|
+
if (!str)
|
|
34
|
+
return ''
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line no-param-reassign
|
|
37
|
+
endChars ??= startChars
|
|
38
|
+
|
|
39
|
+
if (startChars + endChars >= str.length)
|
|
40
|
+
return str
|
|
41
|
+
|
|
42
|
+
const truncatedStr = `${str.substring(0, startChars)}...${str.substring(str.length - endChars)}`
|
|
43
|
+
|
|
44
|
+
return truncatedStr
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 对应链的浏览器地址
|
|
49
|
+
* @param chain 链名
|
|
50
|
+
* @param hash 哈希
|
|
51
|
+
* @param type 哈希类型
|
|
52
|
+
*/
|
|
53
|
+
export function getScanBrowser(chain: string, hash: string, type: 'block' | 'transaction' | 'address' = 'transaction') {
|
|
54
|
+
const _chain = chain.toUpperCase()
|
|
55
|
+
const evmType = type === 'transaction' ? 'tx' : type
|
|
56
|
+
const url
|
|
57
|
+
= {
|
|
58
|
+
TRON: `https://${ENV.isTest ? 'nile.' : ''}tronscan.org/#/${type}/${hash}`,
|
|
59
|
+
ETHEREUM: `https://${ENV.isTest ? 'sepolia.' : ''}etherscan.io/${evmType}/${hash}`,
|
|
60
|
+
BINANCE: `https://${ENV.isTest ? 'testnet.' : ''}bscscan.com/${evmType}/${hash}`,
|
|
61
|
+
}[_chain] ?? ''
|
|
62
|
+
|
|
63
|
+
return url
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Telegram连接
|
|
68
|
+
* @param link 链接
|
|
69
|
+
*/
|
|
70
|
+
export function getTelegramBrowser(link: string) {
|
|
71
|
+
return `https://t.me/${link}`
|
|
72
|
+
}
|
package/vue/index.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|