@peng_kai/kit 0.1.17 → 0.2.0-beta.1
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/adminPlugin.ts +47 -0
- package/admin/components/filter/src/FilterDrawer.vue +153 -153
- package/admin/components/filter/src/FilterParam.vue +76 -78
- package/admin/components/filter/src/FilterReset.vue +2 -2
- package/admin/components/filter/src/useFilterParams.ts +75 -25
- package/admin/components/filter/src/useFilterQuery.ts +32 -16
- package/admin/components/rich-text/index.ts +2 -0
- package/admin/components/rich-text/src/RichText.vue +342 -0
- package/admin/components/rich-text/src/imageUploader.ts +34 -0
- package/admin/components/rich-text/src/type.d.ts +7 -0
- package/admin/components/scroll-nav/index.ts +1 -1
- package/admin/components/scroll-nav/src/ScrollNav.vue +59 -59
- package/admin/components/text/index.ts +13 -13
- package/admin/components/text/src/Amount.vue +121 -121
- package/admin/components/text/src/Datetime.vue +47 -48
- package/admin/components/text/src/Duration.vue +26 -26
- package/admin/components/text/src/Hash.vue +51 -51
- package/admin/components/text/src/createTagGetter.ts +13 -13
- package/admin/components/upload/index.ts +2 -0
- package/admin/components/upload/src/PictureCardUpload.vue +143 -0
- package/admin/components/upload/src/customRequests.ts +31 -0
- package/admin/defines/index.ts +1 -1
- package/admin/defines/route/helpers.ts +0 -1
- package/admin/defines/startup/defineStartup.ts +8 -1
- package/admin/defines/startup/index.ts +1 -1
- package/admin/defines/startup/{getStartups.ts → runStartup.ts} +16 -7
- package/admin/layout/large/Breadcrumb.vue +68 -69
- package/admin/layout/large/Content.vue +23 -24
- package/admin/layout/large/Menu.vue +68 -69
- package/admin/layout/large/PageTab.vue +70 -71
- package/admin/permission/index.ts +2 -4
- package/admin/permission/routerGuard.ts +41 -43
- package/admin/permission/vuePlugin.ts +46 -30
- package/admin/route-guards/collapseMenu.ts +3 -3
- package/admin/route-guards/index.ts +3 -3
- package/admin/route-guards/pageProgress.ts +27 -27
- package/admin/route-guards/pageTitle.ts +18 -19
- package/admin/{hooks/useMenu.ts → stores/createUseMenuStore.ts} +133 -128
- package/admin/{hooks/usePage.ts → stores/createUsePageStore.ts} +145 -141
- package/admin/stores/createUsePageTabStore.ts +43 -0
- package/admin/{permission/usePermission.ts → stores/createUsePermissionStore.ts} +57 -52
- package/admin/stores/index.ts +8 -0
- package/admin/styles/classCover.scss +8 -0
- package/admin/styles/globalCover.scss +54 -54
- package/antd/components/InputNumberRange.vue +59 -59
- package/antd/directives/formLabelAlign.ts +36 -36
- package/antd/hooks/useAntdDrawer.ts +73 -73
- package/antd/hooks/useAntdForm.helpers.ts +92 -8
- package/antd/hooks/useAntdForm.ts +55 -63
- package/antd/hooks/useAntdTable.ts +127 -115
- package/antd/index.ts +1 -1
- package/libs/a-calc.ts +1 -0
- package/libs/axios.ts +2 -0
- package/libs/bignumber.ts +2 -0
- package/libs/dayjs.ts +5 -0
- package/libs/echarts.ts +5 -0
- package/libs/localstorage-slim.ts +2 -0
- package/libs/lodash-es.ts +1 -0
- package/libs/pinia.ts +1 -0
- package/libs/vue-query.ts +1 -0
- package/libs/vueuse.ts +3 -0
- package/package.json +91 -58
- package/request/helpers.ts +68 -49
- package/request/interceptors/toLogin.ts +26 -26
- package/request/queryClient.ts +34 -21
- package/request/type.d.ts +92 -92
- package/stylelint.config.cjs +7 -7
- package/tsconfig.json +50 -50
- package/utils/upload/AwsS3.ts +68 -0
- package/utils/upload/fileHandlers.ts +27 -0
- package/utils/upload/index.ts +2 -0
- package/vite/index.d.ts +1 -0
- package/vite/index.mjs +27 -0
- package/vue/components/echarts/index.ts +1 -0
- package/vue/components/echarts/src/ECharts.vue +48 -0
- package/vue/components/index.ts +1 -0
- package/vue/components/infinite-query/index.ts +1 -1
- package/vue/components/infinite-query/src/InfiniteQuery.vue +199 -205
- package/vue/components/infinite-query/src/useCreateTrigger.ts +39 -39
- package/vue/components/test/KitTest.vue +9 -0
- package/vue/components/test/testStore.ts +11 -0
- package/admin/hooks/index.ts +0 -5
- package/admin/hooks/usePageTab.ts +0 -35
- package/kitDependencies.ts +0 -43
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
3
|
-
import bigNumber from 'bignumber.js';
|
|
4
|
-
import isNil from 'lodash-es/isNil';
|
|
5
|
-
|
|
6
|
-
export const config = {
|
|
7
|
-
aboveColor: '#52c41a',
|
|
8
|
-
belowColor: '#ff4d4f',
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 当 symbol 为以下值时,使用预设的 Logo
|
|
13
|
-
*/
|
|
14
|
-
const presetSymbols: Record<string, string> = {
|
|
15
|
-
USDT: 'https://api.iconify.design/cryptocurrency-color:usdt.svg',
|
|
16
|
-
TRX: 'https://api.iconify.design/cryptocurrency-color:trx.svg',
|
|
17
|
-
USDC: 'https://api.iconify.design/cryptocurrency-color:usdc.svg',
|
|
18
|
-
ETH: 'https://api.iconify.design/cryptocurrency-color:eth.svg',
|
|
19
|
-
BNB: 'https://api.iconify.design/cryptocurrency-color:bnb.svg',
|
|
20
|
-
BUSD: 'https://assets.coingecko.com/coins/images/9576/large/BUSD.png',
|
|
21
|
-
MATIC: 'https://api.iconify.design/cryptocurrency-color:matic.svg',
|
|
22
|
-
SOL: 'https://api.iconify.design/cryptocurrency-color:sol.svg',
|
|
23
|
-
};
|
|
24
|
-
</script>
|
|
25
|
-
|
|
26
|
-
<script setup lang="ts">
|
|
27
|
-
const props = withDefaults(
|
|
28
|
-
defineProps<{
|
|
29
|
-
/** 金额 */
|
|
30
|
-
amount?: string | number
|
|
31
|
-
/** 符号Logo, 可以是币种Logo的URL、法定货币符号($) */
|
|
32
|
-
symbol?: string
|
|
33
|
-
/** 精度 */
|
|
34
|
-
precision?: number
|
|
35
|
-
/** 单位,如币种名称 */
|
|
36
|
-
unit?: string
|
|
37
|
-
/** 保留小数的位数 */
|
|
38
|
-
fractionDigits?: number
|
|
39
|
-
/** 是否填充 0 */
|
|
40
|
-
padZero?: boolean
|
|
41
|
-
/** 金额是否红/绿显示 */
|
|
42
|
-
colorful?: boolean
|
|
43
|
-
/** 是否是大约数 */
|
|
44
|
-
approx?: boolean
|
|
45
|
-
}>(),
|
|
46
|
-
{
|
|
47
|
-
padZero: false,
|
|
48
|
-
fractionDigits: 18,
|
|
49
|
-
colorful: false,
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const amountText = computed(() => {
|
|
54
|
-
const _amount = props.amount;
|
|
55
|
-
|
|
56
|
-
if (isNil(_amount))
|
|
57
|
-
return '-';
|
|
58
|
-
|
|
59
|
-
let bn = bigNumber(_amount);
|
|
60
|
-
bn = !isNil(props.precision) ? bn.dividedBy(10 ** props.precision) : bn;
|
|
61
|
-
let bnt = bn.toFormat(props.fractionDigits);
|
|
62
|
-
bnt = props.padZero ? bnt : bnt.replace(/\.?0+$/, '');
|
|
63
|
-
|
|
64
|
-
return bnt;
|
|
65
|
-
});
|
|
66
|
-
const amountColor = computed(() => {
|
|
67
|
-
const num = Number.parseFloat(props.amount as string);
|
|
68
|
-
|
|
69
|
-
if (!props.colorful || (Number.isNaN(num) ? true : num === 0))
|
|
70
|
-
return '';
|
|
71
|
-
|
|
72
|
-
return num > 0 ? config.aboveColor : (num < 0 ? config.belowColor : '');
|
|
73
|
-
});
|
|
74
|
-
const symbol = computed(() => presetSymbols[props.symbol!] ?? props.symbol ?? '');
|
|
75
|
-
</script>
|
|
76
|
-
|
|
77
|
-
<template>
|
|
78
|
-
<div class="amount-wrapper" :style="{ '--amount-color': amountColor }">
|
|
79
|
-
<!-- 约等于 -->
|
|
80
|
-
<span v-if="props.approx" class="color-$amount-color">≈</span>
|
|
81
|
-
|
|
82
|
-
<!-- 符号 -->
|
|
83
|
-
<img v-if="symbol.startsWith('http')" class="symbol-logo" :src="symbol">
|
|
84
|
-
|
|
85
|
-
<!-- 图片Logo -->
|
|
86
|
-
<span v-else class="color-$amount-color">{{ symbol }}</span> <!-- 文本Logo,如法定币种符号(¥、$) -->
|
|
87
|
-
|
|
88
|
-
<!-- 金额 -->
|
|
89
|
-
<span class="color-$amount-color amount">{{ amountText }}</span>
|
|
90
|
-
|
|
91
|
-
<!-- 单位 -->
|
|
92
|
-
<span v-if="props.unit" class="unit">{{ props.unit }}</span>
|
|
93
|
-
</div>
|
|
94
|
-
</template>
|
|
95
|
-
|
|
96
|
-
<style lang="scss">
|
|
97
|
-
.amount-wrapper {
|
|
98
|
-
display: flex;
|
|
99
|
-
align-items: center;
|
|
100
|
-
|
|
101
|
-
.symbol-logo {
|
|
102
|
-
display: block;
|
|
103
|
-
width: 1.1em;
|
|
104
|
-
height: 1.1em;
|
|
105
|
-
margin-right: 0.2em;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.amount {
|
|
109
|
-
font-family: 'dinm';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.currency-name {
|
|
113
|
-
margin-left: 0.2em;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.unit {
|
|
117
|
-
display: inline-block;
|
|
118
|
-
margin-left: 0.2em;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import bigNumber from 'bignumber.js';
|
|
4
|
+
import isNil from 'lodash-es/isNil';
|
|
5
|
+
|
|
6
|
+
export const config = {
|
|
7
|
+
aboveColor: '#52c41a',
|
|
8
|
+
belowColor: '#ff4d4f',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 当 symbol 为以下值时,使用预设的 Logo
|
|
13
|
+
*/
|
|
14
|
+
const presetSymbols: Record<string, string> = {
|
|
15
|
+
USDT: 'https://api.iconify.design/cryptocurrency-color:usdt.svg',
|
|
16
|
+
TRX: 'https://api.iconify.design/cryptocurrency-color:trx.svg',
|
|
17
|
+
USDC: 'https://api.iconify.design/cryptocurrency-color:usdc.svg',
|
|
18
|
+
ETH: 'https://api.iconify.design/cryptocurrency-color:eth.svg',
|
|
19
|
+
BNB: 'https://api.iconify.design/cryptocurrency-color:bnb.svg',
|
|
20
|
+
BUSD: 'https://assets.coingecko.com/coins/images/9576/large/BUSD.png',
|
|
21
|
+
MATIC: 'https://api.iconify.design/cryptocurrency-color:matic.svg',
|
|
22
|
+
SOL: 'https://api.iconify.design/cryptocurrency-color:sol.svg',
|
|
23
|
+
};
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script setup lang="ts">
|
|
27
|
+
const props = withDefaults(
|
|
28
|
+
defineProps<{
|
|
29
|
+
/** 金额 */
|
|
30
|
+
amount?: string | number
|
|
31
|
+
/** 符号Logo, 可以是币种Logo的URL、法定货币符号($) */
|
|
32
|
+
symbol?: string
|
|
33
|
+
/** 精度 */
|
|
34
|
+
precision?: number
|
|
35
|
+
/** 单位,如币种名称 */
|
|
36
|
+
unit?: string
|
|
37
|
+
/** 保留小数的位数 */
|
|
38
|
+
fractionDigits?: number
|
|
39
|
+
/** 是否填充 0 */
|
|
40
|
+
padZero?: boolean
|
|
41
|
+
/** 金额是否红/绿显示 */
|
|
42
|
+
colorful?: boolean
|
|
43
|
+
/** 是否是大约数 */
|
|
44
|
+
approx?: boolean
|
|
45
|
+
}>(),
|
|
46
|
+
{
|
|
47
|
+
padZero: false,
|
|
48
|
+
fractionDigits: 18,
|
|
49
|
+
colorful: false,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const amountText = computed(() => {
|
|
54
|
+
const _amount = props.amount;
|
|
55
|
+
|
|
56
|
+
if (isNil(_amount))
|
|
57
|
+
return '-';
|
|
58
|
+
|
|
59
|
+
let bn = bigNumber(_amount);
|
|
60
|
+
bn = !isNil(props.precision) ? bn.dividedBy(10 ** props.precision) : bn;
|
|
61
|
+
let bnt = bn.toFormat(props.fractionDigits);
|
|
62
|
+
bnt = props.padZero ? bnt : bnt.replace(/\.?0+$/, '');
|
|
63
|
+
|
|
64
|
+
return bnt;
|
|
65
|
+
});
|
|
66
|
+
const amountColor = computed(() => {
|
|
67
|
+
const num = Number.parseFloat(props.amount as string);
|
|
68
|
+
|
|
69
|
+
if (!props.colorful || (Number.isNaN(num) ? true : num === 0))
|
|
70
|
+
return '';
|
|
71
|
+
|
|
72
|
+
return num > 0 ? config.aboveColor : (num < 0 ? config.belowColor : '');
|
|
73
|
+
});
|
|
74
|
+
const symbol = computed(() => presetSymbols[props.symbol!] ?? props.symbol ?? '');
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<template>
|
|
78
|
+
<div class="amount-wrapper" :style="{ '--amount-color': amountColor }">
|
|
79
|
+
<!-- 约等于 -->
|
|
80
|
+
<span v-if="props.approx" class="color-$amount-color">≈</span>
|
|
81
|
+
|
|
82
|
+
<!-- 符号 -->
|
|
83
|
+
<img v-if="symbol.startsWith('http')" class="symbol-logo" :src="symbol">
|
|
84
|
+
|
|
85
|
+
<!-- 图片Logo -->
|
|
86
|
+
<span v-else class="color-$amount-color">{{ symbol }}</span> <!-- 文本Logo,如法定币种符号(¥、$) -->
|
|
87
|
+
|
|
88
|
+
<!-- 金额 -->
|
|
89
|
+
<span class="color-$amount-color amount">{{ amountText }}</span>
|
|
90
|
+
|
|
91
|
+
<!-- 单位 -->
|
|
92
|
+
<span v-if="props.unit" class="unit">{{ props.unit }}</span>
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<style lang="scss">
|
|
97
|
+
.amount-wrapper {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
|
|
101
|
+
.symbol-logo {
|
|
102
|
+
display: block;
|
|
103
|
+
width: 1.1em;
|
|
104
|
+
height: 1.1em;
|
|
105
|
+
margin-right: 0.2em;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.amount {
|
|
109
|
+
font-family: 'dinm';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.currency-name {
|
|
113
|
+
margin-left: 0.2em;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.unit {
|
|
117
|
+
display: inline-block;
|
|
118
|
+
margin-left: 0.2em;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
@@ -1,48 +1,47 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { Tooltip as ATooltip } from 'ant-design-vue';
|
|
3
|
-
import { computed } from 'vue';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
defineOptions({
|
|
7
|
-
inheritAttrs: false,
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
const props = withDefaults(
|
|
11
|
-
defineProps<{
|
|
12
|
-
timestamp?: number | string
|
|
13
|
-
template?: string
|
|
14
|
-
}>(),
|
|
15
|
-
{
|
|
16
|
-
template: 'MM-DD HH:mm:ss',
|
|
17
|
-
},
|
|
18
|
-
);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<div>{{ dayjs(timestamp).
|
|
38
|
-
|
|
39
|
-
</
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
</style>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Tooltip as ATooltip } from 'ant-design-vue';
|
|
3
|
+
import { computed } from 'vue';
|
|
4
|
+
import { dayjs } from '../../../../utils/date';
|
|
5
|
+
|
|
6
|
+
defineOptions({
|
|
7
|
+
inheritAttrs: false,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
timestamp?: number | string
|
|
13
|
+
template?: string
|
|
14
|
+
}>(),
|
|
15
|
+
{
|
|
16
|
+
template: 'MM-DD HH:mm:ss',
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
const timestamp = computed(() => {
|
|
20
|
+
let tsStr = String(props.timestamp);
|
|
21
|
+
|
|
22
|
+
if (tsStr.length === 10)
|
|
23
|
+
tsStr += '000';
|
|
24
|
+
if (tsStr.length !== 13)
|
|
25
|
+
return;
|
|
26
|
+
|
|
27
|
+
const tsNum = Number.parseInt(tsStr);
|
|
28
|
+
|
|
29
|
+
return Number.isNaN(tsNum) ? undefined : tsNum;
|
|
30
|
+
});
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<ATooltip destroyTooltipOnHide>
|
|
35
|
+
<template v-if="timestamp" #title>
|
|
36
|
+
<div>{{ dayjs(timestamp).fromNow?.() }}</div>
|
|
37
|
+
<div>{{ dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
|
38
|
+
</template>
|
|
39
|
+
<span v-bind="$attrs" class="text">{{ timestamp ? dayjs(timestamp).format(props.template) : '-' }}</span>
|
|
40
|
+
</ATooltip>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<style scoped lang="scss">
|
|
44
|
+
.text {
|
|
45
|
+
font-variant-numeric: tabular-nums;
|
|
46
|
+
}
|
|
47
|
+
</style>
|
|
@@ -1,26 +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>
|
|
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>
|
|
@@ -1,51 +1,51 @@
|
|
|
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
|
-
empty?: string
|
|
19
|
-
}>(),
|
|
20
|
-
{
|
|
21
|
-
hide: true,
|
|
22
|
-
type: 'transaction',
|
|
23
|
-
empty: '-',
|
|
24
|
-
},
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const href = computed(() => {
|
|
28
|
-
const { hash, chain, type } = props;
|
|
29
|
-
|
|
30
|
-
if (!hash || !chain || !type)
|
|
31
|
-
return;
|
|
32
|
-
return getScanBrowser(chain, hash, type);
|
|
33
|
-
});
|
|
34
|
-
const text = computed(() => {
|
|
35
|
-
if (props.hash)
|
|
36
|
-
return props.hide ? desensitize(props.hash) : props.hash;
|
|
37
|
-
else
|
|
38
|
-
return props.empty;
|
|
39
|
-
});
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
<template>
|
|
43
|
-
<ATypographyLink :href="href" :copyable="{ text: props.hash, tooltip: false }" target="_blank">
|
|
44
|
-
<ATooltip>
|
|
45
|
-
<template v-if="props.hide" #title>
|
|
46
|
-
<span class="font-mono">{{ props.hash }}</span>
|
|
47
|
-
</template>
|
|
48
|
-
<span v-bind="$attrs"><slot><span class="font-mono">{{ text }}</span></slot></span>
|
|
49
|
-
</ATooltip>
|
|
50
|
-
</ATypographyLink>
|
|
51
|
-
</template>
|
|
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
|
+
empty?: string
|
|
19
|
+
}>(),
|
|
20
|
+
{
|
|
21
|
+
hide: true,
|
|
22
|
+
type: 'transaction',
|
|
23
|
+
empty: '-',
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const href = computed(() => {
|
|
28
|
+
const { hash, chain, type } = props;
|
|
29
|
+
|
|
30
|
+
if (!hash || !chain || !type)
|
|
31
|
+
return;
|
|
32
|
+
return getScanBrowser(chain, hash, type);
|
|
33
|
+
});
|
|
34
|
+
const text = computed(() => {
|
|
35
|
+
if (props.hash)
|
|
36
|
+
return props.hide ? desensitize(props.hash) : props.hash;
|
|
37
|
+
else
|
|
38
|
+
return props.empty;
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<ATypographyLink :href="href" :copyable="{ text: props.hash, tooltip: false }" target="_blank">
|
|
44
|
+
<ATooltip>
|
|
45
|
+
<template v-if="props.hide" #title>
|
|
46
|
+
<span class="font-mono">{{ props.hash }}</span>
|
|
47
|
+
</template>
|
|
48
|
+
<span v-bind="$attrs"><slot><span class="font-mono">{{ text }}</span></slot></span>
|
|
49
|
+
</ATooltip>
|
|
50
|
+
</ATypographyLink>
|
|
51
|
+
</template>
|
|
@@ -1,13 +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 | string]: [ text: string, color: TagProps['color'] ] }) {
|
|
6
|
-
const typeMap = computed(typeMapFn);
|
|
7
|
-
|
|
8
|
-
return (type: number | string) => {
|
|
9
|
-
const [text, color] = typeMap.value[type] ?? [];
|
|
10
|
-
|
|
11
|
-
return text ? h(ATag, { color }, () => text) : h('span', null, '-');
|
|
12
|
-
};
|
|
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 | string]: [ text: string, color: TagProps['color'] ] }) {
|
|
6
|
+
const typeMap = computed(typeMapFn);
|
|
7
|
+
|
|
8
|
+
return (type: number | string) => {
|
|
9
|
+
const [text, color] = typeMap.value[type] ?? [];
|
|
10
|
+
|
|
11
|
+
return text ? h(ATag, { color }, () => text) : h('span', null, '-');
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { message } from 'ant-design-vue';
|
|
3
|
+
import type { UploadProps } from 'ant-design-vue';
|
|
4
|
+
import type { PreviewGroupPreview } from 'ant-design-vue/es/vc-image/src/PreviewGroup';
|
|
5
|
+
import { useVModel } from '@vueuse/core';
|
|
6
|
+
import { ref } from 'vue';
|
|
7
|
+
import type { AwsS3 } from '../../../../utils/upload';
|
|
8
|
+
import { createAwsS3Request } from './customRequests';
|
|
9
|
+
|
|
10
|
+
type UploadFile = Parameters<NonNullable<UploadProps['onPreview']>>['0'];
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script lang="ts" setup>
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
/** 上传文件的数组 */
|
|
17
|
+
modelValue?: UploadFile[]
|
|
18
|
+
/** awsS3 实例对象 */
|
|
19
|
+
awsS3: AwsS3
|
|
20
|
+
/** 根目录 */
|
|
21
|
+
rootDir?: string
|
|
22
|
+
/** 可以上传的最大文件数 */
|
|
23
|
+
maxCount?: number
|
|
24
|
+
/** 显示图片卡的行数,或 “auto-fill” 以根据可用空间自动调整行数。 */
|
|
25
|
+
rowCount?: number | 'auto-fill'
|
|
26
|
+
/** 图片卡的大小 */
|
|
27
|
+
cardSize?: string
|
|
28
|
+
/** 允许的图片后缀 */
|
|
29
|
+
allowExts?: string[]
|
|
30
|
+
}>(),
|
|
31
|
+
{
|
|
32
|
+
modelValue: () => [],
|
|
33
|
+
maxCount: 2,
|
|
34
|
+
rowCount: 'auto-fill',
|
|
35
|
+
cardSize: '90px',
|
|
36
|
+
allowExts: () => ['jpg', 'jpeg', 'png', 'gif', 'apng', 'webp', 'svg'],
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
const emits = defineEmits<{
|
|
40
|
+
(e: 'update:modelValue', value: UploadFile[]): void
|
|
41
|
+
}>();
|
|
42
|
+
|
|
43
|
+
const fileList = useVModel(props, 'modelValue', emits);
|
|
44
|
+
const preview = ref<PreviewGroupPreview>({
|
|
45
|
+
visible: false,
|
|
46
|
+
onVisibleChange(value) {
|
|
47
|
+
preview.value.visible = value;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function handlePreview(file: UploadFile) {
|
|
52
|
+
preview.value.src = file.url || file.thumbUrl;
|
|
53
|
+
preview.value.visible = true;
|
|
54
|
+
preview.value.current = fileList.value?.findIndex(item => preview.value.src === (item.url || item.thumbUrl)) ?? 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function beforeUpload(file: UploadFile) {
|
|
58
|
+
const isImg = props.allowExts.some(item => String(file.type).toLowerCase().includes(item));
|
|
59
|
+
|
|
60
|
+
if (!isImg)
|
|
61
|
+
message.error(`${file.name} 不是图片 (${props.allowExts.join(',')})`);
|
|
62
|
+
|
|
63
|
+
return isImg;
|
|
64
|
+
}
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<div :class="{ 'w-fit': typeof props.rowCount === 'number' }">
|
|
69
|
+
<AUpload
|
|
70
|
+
v-model:fileList="fileList" class="pic-card-upload" listType="picture-card" multiple :maxCount="$props.maxCount"
|
|
71
|
+
:beforeUpload="beforeUpload" :customRequest="createAwsS3Request($props.awsS3, $props.rootDir)" @preview="handlePreview"
|
|
72
|
+
>
|
|
73
|
+
<i v-if="fileList.length < props.maxCount" class="i-ant-design:plus-outlined text-26px text-$antd-colorTextTertiary" />
|
|
74
|
+
</AUpload>
|
|
75
|
+
|
|
76
|
+
<div style="display: none">
|
|
77
|
+
<AImagePreviewGroup :preview="preview">
|
|
78
|
+
<AImage
|
|
79
|
+
v-for="item of fileList"
|
|
80
|
+
:key="item.uid"
|
|
81
|
+
:src="item.url || item.thumbUrl"
|
|
82
|
+
/>
|
|
83
|
+
</AImagePreviewGroup>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<style scoped lang="scss">
|
|
89
|
+
/* stylelint-disable function-no-unknown */
|
|
90
|
+
|
|
91
|
+
.pic-card-upload.ant-upload-wrapper {
|
|
92
|
+
display: block;
|
|
93
|
+
|
|
94
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item) {
|
|
95
|
+
padding: 0;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:deep(.ant-upload-list::before) {
|
|
100
|
+
display: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
:deep(.ant-upload-list-picture-card) {
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: repeat(v-bind('$props.rowCount'), v-bind('$props.cardSize'));
|
|
106
|
+
gap: 10px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-container),
|
|
110
|
+
:deep(.ant-upload.ant-upload-select) {
|
|
111
|
+
width: v-bind('$props.cardSize');
|
|
112
|
+
height: v-bind('$props.cardSize');
|
|
113
|
+
margin: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item::before ) {
|
|
117
|
+
width: 100%;
|
|
118
|
+
height: 100%;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-thumbnail img) {
|
|
122
|
+
object-fit: cover;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-progress) {
|
|
126
|
+
bottom: calc(50% - 22px);
|
|
127
|
+
left: 50%;
|
|
128
|
+
line-height: 1em;
|
|
129
|
+
transform: translateX(-50%);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-thumbnail) {
|
|
133
|
+
display: flex;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:deep(.ant-upload-list.ant-upload-list-picture-card .ant-upload-list-item-file+.ant-upload-list-item-name) {
|
|
139
|
+
left: 50%;
|
|
140
|
+
transform: translateX(-50%);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
</style>
|