This commit is contained in:
2026-06-19 12:53:39 +08:00
parent 2d50c57ef0
commit ac6f223eb2
4 changed files with 421 additions and 0 deletions

View File

@@ -1,2 +1,3 @@
export * from './feature'; export * from './feature';
export * from './product'; export * from './product';
export * from './query-whitelist';

View File

@@ -0,0 +1,74 @@
import type { Recordable } from '@vben/types';
import { requestClient } from '#/api/request';
export namespace QueryWhitelistApi {
export interface OpRequest {
name: string;
id_card: string;
api_codes: string[];
remark?: string;
}
export interface OpResponse {
success: boolean;
tianyuan_code: number;
tianyuan_message: string;
transaction_id?: string;
entry_id?: string;
id_card_masked?: string;
entry_api_codes?: string[];
entry_status?: string;
}
export interface OpLogItem {
id: number;
admin_user_id: number;
admin_user_name: string;
action: string;
name: string;
id_card: string;
id_card_masked: string;
api_codes: string[];
remark: string;
tianyuan_code: number;
tianyuan_message: string;
transaction_id: string;
entry_id: string;
entry_status: string;
entry_api_codes: string[];
create_time: string;
}
export interface OpLogList {
total: number;
items: OpLogItem[];
}
}
async function createQueryWhitelist(data: QueryWhitelistApi.OpRequest) {
return requestClient.post<QueryWhitelistApi.OpResponse>(
'/query-whitelist/create',
data,
);
}
async function appendQueryWhitelist(data: QueryWhitelistApi.OpRequest) {
return requestClient.post<QueryWhitelistApi.OpResponse>(
'/query-whitelist/append',
data,
);
}
async function getQueryWhitelistOpLogList(params: Recordable<any>) {
return requestClient.get<QueryWhitelistApi.OpLogList>(
'/query-whitelist/op-log/list',
{ params },
);
}
export {
appendQueryWhitelist,
createQueryWhitelist,
getQueryWhitelistOpLogList,
};

View File

@@ -0,0 +1,162 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { QueryWhitelistApi } from '#/api/product-manage/query-whitelist';
export function useConfigFormSchema(
featureOptions: Array<{ label: string; value: string }>,
): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'id_card',
label: '身份证号',
rules: 'required',
componentProps: {
maxlength: 18,
placeholder: '18位中国大陆身份证号',
},
},
{
component: 'Input',
fieldName: 'name',
label: '姓名',
defaultValue: '*',
rules: 'required',
componentProps: {
placeholder: '填 * 表示只按身份证匹配',
},
},
{
component: 'Select',
fieldName: 'api_codes',
label: '产品编码',
rules: 'required',
componentProps: {
mode: 'multiple',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
options: featureOptions,
placeholder: '请选择产品编码',
},
},
{
component: 'Textarea',
fieldName: 'remark',
label: '备注',
componentProps: {
maxlength: 500,
rows: 2,
showCount: true,
placeholder: '可选备注',
},
},
];
}
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'id_card',
label: '身份证号',
},
{
component: 'Select',
fieldName: 'action',
label: '操作类型',
componentProps: {
allowClear: true,
options: [
{ label: '创建规则', value: 'create' },
{ label: '追加接口', value: 'append' },
],
},
},
{
component: 'InputNumber',
fieldName: 'tianyuan_code',
label: '天远返回码',
componentProps: {
class: 'w-full',
},
},
];
}
export function useColumns(): VxeTableGridOptions['columns'] {
return [
{
field: 'create_time',
title: '操作时间',
minWidth: 170,
},
{
field: 'admin_user_name',
title: '操作人',
minWidth: 100,
},
{
field: 'action',
title: '操作类型',
minWidth: 100,
formatter: ({ cellValue }) => (cellValue === 'append' ? '追加接口' : '创建规则'),
},
{
field: 'id_card',
title: '身份证号',
minWidth: 180,
},
{
field: 'name',
title: '姓名规则',
minWidth: 90,
},
{
field: 'api_codes',
title: '提交编码',
minWidth: 220,
formatter: ({ cellValue }) =>
Array.isArray(cellValue) ? cellValue.join(', ') : '',
},
{
field: 'tianyuan_code',
title: '天远码',
minWidth: 80,
},
{
field: 'tianyuan_message',
title: '天远描述',
minWidth: 180,
},
{
field: 'entry_api_codes',
title: '规则编码',
minWidth: 220,
formatter: ({ cellValue }) =>
Array.isArray(cellValue) ? cellValue.join(', ') : '',
},
{
field: 'remark',
title: '备注',
minWidth: 120,
},
];
}
export function formatOpResult(result: QueryWhitelistApi.OpResponse | null) {
if (!result) {
return '暂无调用结果';
}
const lines = [
`业务结果:${result.success ? '成功' : '失败'}`,
`天远码:${result.tianyuan_code}`,
`描述:${result.tianyuan_message || '-'}`,
`流水号:${result.transaction_id || '-'}`,
`规则ID${result.entry_id || '-'}`,
`脱敏身份证:${result.id_card_masked || '-'}`,
`规则状态:${result.entry_status || '-'}`,
`规则编码:${result.entry_api_codes?.join(', ') || '-'}`,
];
return lines.join('\n');
}

View File

@@ -0,0 +1,184 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { FeatureApi, QueryWhitelistApi } from '#/api/product-manage';
import { onMounted, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button, Card, message, Space } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
appendQueryWhitelist,
createQueryWhitelist,
getFeatureList,
getQueryWhitelistOpLogList,
} from '#/api/product-manage';
import { formatOpResult, useColumns, useConfigFormSchema, useGridFormSchema } from './data';
const featureOptions = ref<Array<{ label: string; value: string }>>([]);
const featureItems = ref<FeatureApi.FeatureItem[]>([]);
const lastResult = ref<QueryWhitelistApi.OpResponse | null>(null);
const submitting = ref(false);
const configFormSchema = ref(useConfigFormSchema([]));
const [ConfigForm, configFormApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
},
schema: configFormSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-1 md:grid-cols-2',
});
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
submitOnChange: true,
},
gridOptions: {
columns: useColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getQueryWhitelistOpLogList({
page: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
} as VxeTableGridOptions<QueryWhitelistApi.OpLogItem>,
});
async function loadFeatureOptions() {
const { items } = await getFeatureList({
page: 1,
pageSize: 500,
});
featureItems.value = items || [];
featureOptions.value = featureItems.value.map((item) => ({
label: `${item.api_id}${item.name}`,
value: item.api_id,
}));
configFormSchema.value = useConfigFormSchema(featureOptions.value);
configFormApi.setState({ schema: configFormSchema.value });
}
function refreshLogs() {
gridApi.query();
}
async function submitAction(action: 'append' | 'create') {
const { valid } = await configFormApi.validate();
if (!valid) {
return;
}
const values = await configFormApi.getValues();
const apiCodes = Array.isArray(values.api_codes) ? values.api_codes : [];
if (apiCodes.length === 0) {
message.warning('请至少选择一个产品编码');
return;
}
const payload = {
name: values.name || '*',
id_card: values.id_card,
api_codes: apiCodes,
remark: values.remark || '',
};
submitting.value = true;
try {
const result =
action === 'create'
? await createQueryWhitelist(payload)
: await appendQueryWhitelist(payload);
lastResult.value = result;
if (result.success) {
message.success(action === 'create' ? '创建规则成功' : '追加接口成功');
refreshLogs();
} else {
message.warning(result.tianyuan_message || '天远接口返回失败');
refreshLogs();
}
} finally {
submitting.value = false;
}
}
function selectJudicialFeatures() {
const judicialCodes = featureItems.value
.filter((item) => item.name.includes('司法'))
.map((item) => item.api_id);
if (judicialCodes.length === 0) {
message.info('当前模块列表中未找到名称包含「司法」的模块');
return;
}
configFormApi.setFieldValue('api_codes', judicialCodes);
}
function clearApiCodes() {
configFormApi.setFieldValue('api_codes', []);
}
onMounted(async () => {
try {
await loadFeatureOptions();
} catch {
message.error('加载模块列表失败');
}
});
</script>
<template>
<Page auto-content-height>
<div class="flex flex-col gap-4">
<Card title="查询白名单配置">
<ConfigForm />
<div class="mt-4 flex flex-wrap items-center justify-between gap-3">
<Space wrap>
<Button
type="primary"
:loading="submitting"
@click="submitAction('create')"
>
创建规则
</Button>
<Button :loading="submitting" @click="submitAction('append')">
追加接口
</Button>
<Button @click="selectJudicialFeatures">一键选中司法</Button>
<Button @click="clearApiCodes">清空编码</Button>
</Space>
</div>
</Card>
<Card title="最近一次天远 API 返回结果">
<pre class="whitespace-pre-wrap text-sm leading-6">{{ formatOpResult(lastResult) }}</pre>
</Card>
<Grid table-title="操作记录" />
</div>
</Page>
</template>