Skip to content

Commit

Permalink
Merge pull request #16 from MonteCarloClub/history-onchain
Browse files Browse the repository at this point in the history
查询证书的链上信息,历史颁发记录
  • Loading branch information
jindada1 authored Apr 5, 2023
2 parents e730074 + ddd1af2 commit 6b5330a
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 33 deletions.
27 changes: 27 additions & 0 deletions fe/src/api/cert/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,30 @@ export function verify(data: API.VerifyParams) {
}
);
}


/**
* 查询证书链上状态
*/
export function statusOnChain(params: API.QueryParams) {
return request<API.CertOnChainResponse>(
{
url: '/GetCertificateFromFabric',
method: 'get',
params
}
);
}

/**
* 查询证书链上状态
*/
export function history(params: API.HistoryParams) {
return request<API.HistoryResponse>(
{
url: '/getCertificates',
method: 'get',
params
}
);
}
11 changes: 11 additions & 0 deletions fe/src/api/cert/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,15 @@ declare namespace API {
type VerifyParams = string;

type VerifyResponse = string;

type CertOnChainResponse = string | Cert;

type HistoryParams = {
index: number;
count: number;
};

type HistoryResponse = {
certificates: string[];
};
}
82 changes: 82 additions & 0 deletions fe/src/components/CertHistory.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { history } from "@/api/cert";
const modalVisible = ref(false);
const loadingMore = ref(false);
const hasMore = ref(true);
const list = ref<string[]>([])
let index = 0;
const count = 10;
function fetchNext() {
history({
count,
index
}).then((res) => {
const { certificates } = res;
list.value = list.value.concat(certificates);
index += count;
// 不够的时候,认为后续没有了
if (certificates.length < count) {
hasMore.value = false;
}
}).catch(console.log)
}
onMounted(() => {
fetchNext();
});
</script>

<template>
<a-modal v-model:visible="modalVisible" centered :footer="null" title="证书颁发记录" @ok="modalVisible = false">
<div class="scrollable">
<p v-for="cert in list" :key="cert">{{ cert }}</p>
<div style="text-align: center;">
<a-button v-if="hasMore" type="link" :loading="loadingMore" @click="fetchNext">获取更多</a-button>
<p v-else style="color: gray">没有更多了</p>
</div>
</div>
</a-modal>
<a-button class="link-btn" type="link" size="large" @click="modalVisible = true"> 历史颁发记录 </a-button>
</template>

<style scoped>
.link-btn {
padding: 0;
}
.scrollable {
height: 300px;
overflow-y: auto;
}
.scrollable::-webkit-scrollbar {
width: 5px;
height: 5px;
}
.scrollable::-webkit-scrollbar-thumb {
background-color: lightgray;
border-radius: 2px;
}
.scrollable::-webkit-scrollbar-track {
background-color: transparent;
}
.scrollable-hidden {
overflow: auto;
}
.scrollable-hidden::-webkit-scrollbar {
width: 0px;
height: 0px;
}
.scrollable-hidden::-webkit-scrollbar-track {
background-color: transparent;
}
</style>
119 changes: 119 additions & 0 deletions fe/src/components/OnChainCert.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<script setup lang="ts">
import { ref } from "vue";
import { message } from "ant-design-vue";
import CertDetail from "@/components/CertDetail.vue";
import SearchInput from "@/components/SearchInput.vue";
import { statusOnChain } from "@/api/cert";
import { SEARCH_PLACE_HOLDER } from "@/common/constants";
import { isObject } from "@/utils/is";
const modalVisible = ref(false);
const cert = ref<API.Cert>();
const inputKey = ref('')
const first = ref(false)
const loading = ref(false);
const hasResult = ref(false);
const revokedTime = ref('');
const searchOnChain = (searchKey: string) => {
const s = searchKey;
if (s.length < 1) {
message.error('请输入证书编号')
return;
}
first.value = true;
inputKey.value = s;
loading.value = true;
statusOnChain({
no: searchKey
}).then((res) => {
hasResult.value = true;
// 如果未撤销,是 object
if (isObject(res)) {
revokedTime.value = ''
cert.value = res
return;
}
// 如果是 string
const regexp = /\[Revoked at (.*)\] (.*)/g;
const matches = Array.from(res.matchAll(regexp));
if (matches.length > 0 && matches[0].length === 3) {
// 撤销时间
revokedTime.value = matches[0][1];
// 证书信息
cert.value = JSON.parse(JSON.parse(matches[0][2]))
}
else {
hasResult.value = false;
}
}).catch(() => {
hasResult.value = false;
}).finally(() => {
loading.value = false;
})
}
</script>

<template>
<a-modal v-model:visible="modalVisible" centered :footer="null" title="查询证书的链上信息" @ok="modalVisible = false">
<div class="panel">
<SearchInput :placeholder="SEARCH_PLACE_HOLDER" @search="searchOnChain" />
<div v-if="first">
<div v-if="loading" class="loading-area">
<a-spin tip="正在检索" />
</div>
<div v-else>
<a-card v-if="hasResult" class="cert-card" title="证书详情">
<template #extra>
<a-tooltip v-if="revokedTime" placement="left" color="#f50">
<template #title>
<span> 撤销时间为:{{ revokedTime }} </span>
</template>
<a-tag color="#f50">已撤销</a-tag>
</a-tooltip>
<a-tag v-else color="#87d068">有效</a-tag>
</template>
<CertDetail :cert="cert" />
</a-card>
<div v-else>
<a-empty style="margin-top: 100px">
<template #description>
<span>未搜索到编号为</span>
<a-tag> {{ inputKey }} </a-tag>
<span>的证书</span>
</template>
</a-empty>
</div>
</div>
</div>
</div>
</a-modal>
<a-button class="link-btn" type="link" size="large" @click="modalVisible = true"> 查询链上证书 </a-button>
</template>

<style scoped>
.link-btn {
padding: 0;
}
.panel {
margin: 0 auto;
}
.cert-card {
margin-top: 22px;
text-align: left;
}
.panel .ant-card-meta {
margin-bottom: 22px;
}
.loading-area {
margin-top: 120px;
text-align: center;
}
</style>
12 changes: 8 additions & 4 deletions fe/src/components/SearchInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
type="text"
required
v-model="searchKey"
@keydown.enter="onSearch(searchKey)"
@keydown.enter="onSearch()"
:placeholder="dynamicPlaceholder ? '' : placeholder"
:style="dark ? 'color: white;':''"
/>
Expand All @@ -13,7 +13,7 @@
type="link"
:class="`search-icon ${dark ? 'dark-search-icon' : 'light-search-icon'}`"
size="large"
@click="onSearch(searchKey)"
@click="onSearch()"
>
<template #icon>
<SearchOutlined />
Expand All @@ -28,8 +28,7 @@
</template>

<script setup lang="ts">
import { ref } from "vue";
import { onSearch } from "@/composition/useSearch";
import { ref, defineEmits } from "vue";
import { SearchOutlined } from "@ant-design/icons-vue";
defineProps({
Expand All @@ -48,6 +47,11 @@ defineProps({
});
const searchKey = ref("");
const emit = defineEmits(['search'])
const onSearch = () => {
emit('search', searchKey.value);
}
</script>

<style scoped>
Expand Down
8 changes: 7 additions & 1 deletion fe/src/components/VerifyCert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@ function verifyCert() {
</script>

<template>
<a-button type="link" size="large" @click="verifyCert"> 验证本地证书 </a-button>
<a-button class="link-btn" type="link" size="large" @click="verifyCert"> 验证本地证书 </a-button>
</template>

<style scoped>
.link-btn {
padding: 0;
}
</style>
12 changes: 6 additions & 6 deletions fe/src/pages/Certs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Modal, message } from "ant-design-vue";
import { SEARCH_PLACE_HOLDER } from "@/common/constants";
import { getStorage, setStorage } from "@/utils/storage";
import { SearchOutlined } from "@ant-design/icons-vue";
import { onSearch } from "@/composition/useSearch";
const certs = ref<API.Cert[]>([]);
Expand Down Expand Up @@ -158,7 +159,8 @@ const handleReset = (clearFilters: (config: any) => void) => {
<div class="flex-expand nav">
<router-link to="/"> 首页 </router-link>
</div>
<SearchInput class="search-container" :dynamic-placeholder="false" :placeholder="SEARCH_PLACE_HOLDER" />
<SearchInput class="search-container" :dynamic-placeholder="false" :placeholder="SEARCH_PLACE_HOLDER"
@search="onSearch" />
<div class="flex-expand nav" style="text-align: right;">
<router-link to="/create"> 创建证书 </router-link>
</div>
Expand All @@ -169,15 +171,13 @@ const handleReset = (clearFilters: (config: any) => void) => {

<template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div style="padding: 8px; width: 240px;">
<a-input ref="searchInput" placeholder="筛选序号中包含关键字的证书" :value="selectedKeys[0]"
style="margin-bottom: 8px;"
<a-input ref="searchInput" placeholder="筛选序号中包含关键字的证书" :value="selectedKeys[0]" style="margin-bottom: 8px;"
@change="(e: any) => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="handleSearch(confirm, column.dataIndex)" />
<a-button type="primary" style="margin-right: 8px"
@click="handleSearch(confirm, column.dataIndex)">
<a-button type="primary" style="margin-right: 8px" @click="handleSearch(confirm, column.dataIndex)">
筛选
</a-button>
<a-button @click="handleReset(clearFilters)">
<a-button @click="handleReset(clearFilters)">
清空筛选条件
</a-button>
</div>
Expand Down
Loading

0 comments on commit 6b5330a

Please # to comment.