add
This commit is contained in:
5288
pnpm-lock.yaml
generated
Normal file
5288
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,10 @@ import { announcementApi } from './announcement.js'
|
|||||||
import { articleApi } from './article.js'
|
import { articleApi } from './article.js'
|
||||||
import { balanceAlertApi } from './balanceAlertApi.js'
|
import { balanceAlertApi } from './balanceAlertApi.js'
|
||||||
import { adminInvoiceApi, invoiceApi } from './invoice.js'
|
import { adminInvoiceApi, invoiceApi } from './invoice.js'
|
||||||
|
import { queryWhitelistApi } from './queryWhitelist.js'
|
||||||
|
|
||||||
// 直接导出发票API、文章API、公告API和余额预警API
|
// 直接导出发票API、文章API、公告API和余额预警API
|
||||||
export { adminInvoiceApi, announcementApi, articleApi, balanceAlertApi, invoiceApi }
|
export { adminInvoiceApi, announcementApi, articleApi, balanceAlertApi, invoiceApi, queryWhitelistApi }
|
||||||
|
|
||||||
// 用户相关接口 - 严格按照后端路由定义
|
// 用户相关接口 - 严格按照后端路由定义
|
||||||
export const userApi = {
|
export const userApi = {
|
||||||
@@ -403,5 +404,6 @@ export default {
|
|||||||
whiteList: whiteListApi,
|
whiteList: whiteListApi,
|
||||||
api: apiApi,
|
api: apiApi,
|
||||||
invoice: invoiceApi,
|
invoice: invoiceApi,
|
||||||
adminInvoice: adminInvoiceApi
|
adminInvoice: adminInvoiceApi,
|
||||||
|
queryWhitelist: queryWhitelistApi
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/api/queryWhitelist.js
Normal file
13
src/api/queryWhitelist.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export const queryWhitelistApi = {
|
||||||
|
getEntries: (params) => request.get('/admin/query-whitelist/entries', { params }),
|
||||||
|
getEntry: (id) => request.get(`/admin/query-whitelist/entries/${id}`),
|
||||||
|
createEntry: (data) => request.post('/admin/query-whitelist/entries', data),
|
||||||
|
updateEntry: (id, data) => request.put(`/admin/query-whitelist/entries/${id}`, data),
|
||||||
|
updateEntryStatus: (id, status) => request.patch(`/admin/query-whitelist/entries/${id}/status`, { status }),
|
||||||
|
deleteEntry: (id) => request.delete(`/admin/query-whitelist/entries/${id}`),
|
||||||
|
importLegacy: () => request.post('/admin/query-whitelist/entries/import-legacy')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default queryWhitelistApi
|
||||||
@@ -168,6 +168,7 @@ export const getUserAccessibleMenuItems = (userType = 'user', _isCertified = fal
|
|||||||
children: [
|
children: [
|
||||||
{ name: '系统统计', path: '/admin/statistics', icon: ChartBar },
|
{ name: '系统统计', path: '/admin/statistics', icon: ChartBar },
|
||||||
{ name: '企业审核', path: '/admin/certification-reviews', icon: ShieldCheck },
|
{ name: '企业审核', path: '/admin/certification-reviews', icon: ShieldCheck },
|
||||||
|
{ name: '查询白名单', path: '/admin/query-whitelist', icon: ShieldCheck },
|
||||||
{ name: '产品管理', path: '/admin/products', icon: Cube },
|
{ name: '产品管理', path: '/admin/products', icon: Cube },
|
||||||
{ name: '用户管理', path: '/admin/users', icon: Users },
|
{ name: '用户管理', path: '/admin/users', icon: Users },
|
||||||
{ name: '分类管理', path: '/admin/categories', icon: Tag },
|
{ name: '分类管理', path: '/admin/categories', icon: Tag },
|
||||||
|
|||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="isEdit ? '编辑查询白名单' : '新增查询白名单'"
|
||||||
|
width="640px"
|
||||||
|
destroy-on-close
|
||||||
|
@closed="handleClosed"
|
||||||
|
>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
|
||||||
|
<el-form-item label="生效范围" prop="scope">
|
||||||
|
<el-radio-group v-model="form.scope" @change="handleScopeChange">
|
||||||
|
<el-radio value="global">全局(所有用户)</el-radio>
|
||||||
|
<el-radio value="user">指定用户</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="form.scope === 'user'" label="用户" prop="user_id">
|
||||||
|
<el-select
|
||||||
|
v-model="form.user_id"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
reserve-keyword
|
||||||
|
placeholder="搜索手机号或企业名"
|
||||||
|
:remote-method="searchUsers"
|
||||||
|
:loading="userLoading"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="user in userOptions"
|
||||||
|
:key="user.id"
|
||||||
|
:label="formatUserLabel(user)"
|
||||||
|
:value="user.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="姓名" prop="name">
|
||||||
|
<el-input v-model="form.name" placeholder="输入姓名;填 * 表示仅匹配身份证" />
|
||||||
|
<div class="form-tip">填 <code>*</code> 时只校验身份证,不校验姓名(兼容历史硬编码逻辑)</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="身份证号" prop="id_card">
|
||||||
|
<el-input
|
||||||
|
v-model="form.id_card"
|
||||||
|
placeholder="18位身份证号"
|
||||||
|
maxlength="18"
|
||||||
|
:disabled="isEdit"
|
||||||
|
/>
|
||||||
|
<div v-if="isEdit" class="form-tip">编辑时不修改身份证号;如需变更请删除后重新添加</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="生效接口" prop="api_codes">
|
||||||
|
<el-select
|
||||||
|
v-model="form.api_codes"
|
||||||
|
multiple
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
default-first-option
|
||||||
|
placeholder="选择或输入 API 编码"
|
||||||
|
class="w-full"
|
||||||
|
@change="handleApiCodesChange"
|
||||||
|
>
|
||||||
|
<el-option label="全部接口 (*)" value="*" />
|
||||||
|
<el-option
|
||||||
|
v-for="product in productOptions"
|
||||||
|
:key="product.code"
|
||||||
|
:label="`${product.code} - ${product.name}`"
|
||||||
|
:value="product.code"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<div class="form-tip">选择「全部接口」时仅对入参<strong>必填身份证</strong>的 API 生效,企业类等无身份证入参的接口不受影响</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="备注">
|
||||||
|
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选备注" maxlength="500" show-word-limit />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { productAdminApi, queryWhitelistApi, userApi } from '@/api'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: Boolean,
|
||||||
|
entry: { type: Object, default: null }
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'success'])
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => emit('update:modelValue', val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEdit = computed(() => !!props.entry?.id)
|
||||||
|
const formRef = ref(null)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const userLoading = ref(false)
|
||||||
|
const userOptions = ref([])
|
||||||
|
const productOptions = ref([])
|
||||||
|
|
||||||
|
const defaultForm = () => ({
|
||||||
|
scope: 'global',
|
||||||
|
user_id: '*',
|
||||||
|
name: '*',
|
||||||
|
id_card: '',
|
||||||
|
api_codes: ['*'],
|
||||||
|
remark: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const form = reactive(defaultForm())
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
scope: [{ required: true, message: '请选择生效范围', trigger: 'change' }],
|
||||||
|
user_id: [{
|
||||||
|
validator: (_rule, value, callback) => {
|
||||||
|
if (form.scope === 'user' && !value) {
|
||||||
|
callback(new Error('请选择用户'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'change'
|
||||||
|
}],
|
||||||
|
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||||
|
id_card: [{
|
||||||
|
validator: (_rule, value, callback) => {
|
||||||
|
if (isEdit.value) {
|
||||||
|
callback()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!value || value.length !== 18) {
|
||||||
|
callback(new Error('请输入18位身份证号'))
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}],
|
||||||
|
api_codes: [{ required: true, type: 'array', min: 1, message: '请选择至少一个接口', trigger: 'change' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatUserLabel = (user) => {
|
||||||
|
const phone = user.phone || '-'
|
||||||
|
const company = user.enterprise_info?.company_name
|
||||||
|
return company ? `${phone}(${company})` : phone
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScopeChange = (scope) => {
|
||||||
|
form.user_id = scope === 'global' ? '*' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleApiCodesChange = (codes) => {
|
||||||
|
if (codes.includes('*') && codes.length > 1) {
|
||||||
|
form.api_codes = ['*']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchUsers = async (query) => {
|
||||||
|
if (!query) return
|
||||||
|
userLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await userApi.getUserList({ phone: query, page: 1, page_size: 20 })
|
||||||
|
userOptions.value = res.data?.items || []
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
userLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadProducts = async () => {
|
||||||
|
try {
|
||||||
|
const res = await productAdminApi.getAvailableProducts({ page: 1, page_size: 500 })
|
||||||
|
productOptions.value = res.data?.items || res.data || []
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载产品列表失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fillForm = (entry) => {
|
||||||
|
Object.assign(form, defaultForm())
|
||||||
|
if (!entry) return
|
||||||
|
form.scope = entry.is_global ? 'global' : 'user'
|
||||||
|
form.user_id = entry.user_id
|
||||||
|
form.name = entry.name
|
||||||
|
form.api_codes = [...(entry.api_codes || ['*'])]
|
||||||
|
form.remark = entry.remark || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (open) => {
|
||||||
|
if (open) {
|
||||||
|
fillForm(props.entry)
|
||||||
|
loadProducts()
|
||||||
|
if (props.entry && !props.entry.is_global) {
|
||||||
|
userOptions.value = [{ id: props.entry.user_id, phone: props.entry.user_id }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
user_id: form.scope === 'global' ? '*' : form.user_id,
|
||||||
|
name: form.name.trim(),
|
||||||
|
api_codes: form.api_codes,
|
||||||
|
remark: form.remark
|
||||||
|
}
|
||||||
|
if (!isEdit.value) {
|
||||||
|
payload.id_card = form.id_card.trim()
|
||||||
|
await queryWhitelistApi.createEntry(payload)
|
||||||
|
ElMessage.success('创建成功')
|
||||||
|
} else {
|
||||||
|
await queryWhitelistApi.updateEntry(props.entry.id, {
|
||||||
|
name: payload.name,
|
||||||
|
api_codes: payload.api_codes,
|
||||||
|
remark: payload.remark
|
||||||
|
})
|
||||||
|
ElMessage.success('更新成功')
|
||||||
|
}
|
||||||
|
visible.value = false
|
||||||
|
emit('success')
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(e.message || '操作失败')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClosed = () => {
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
Object.assign(form, defaultForm())
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-tip {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #6b7280;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-tip code {
|
||||||
|
background: #f3f4f6;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
313
src/pages/admin/query-whitelist/index.vue
Normal file
313
src/pages/admin/query-whitelist/index.vue
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
<template>
|
||||||
|
<ListPageLayout
|
||||||
|
title="查询白名单"
|
||||||
|
subtitle="配置命中后返回「查询为空」的规则;「全部接口」仅作用于入参必填身份证的 API"
|
||||||
|
>
|
||||||
|
<template #actions>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<el-button :loading="importing" @click="handleImportLegacy">
|
||||||
|
导入历史硬编码
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" @click="handleCreate">
|
||||||
|
<PlusIcon class="w-4 h-4 mr-1" />
|
||||||
|
新增规则
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #filters>
|
||||||
|
<FilterSection>
|
||||||
|
<FilterItem label="生效范围">
|
||||||
|
<el-select v-model="filters.scope" clearable placeholder="全部" class="w-full" @change="handleFilterChange">
|
||||||
|
<el-option label="全局规则" value="global" />
|
||||||
|
<el-option label="指定用户" value="user" />
|
||||||
|
</el-select>
|
||||||
|
</FilterItem>
|
||||||
|
|
||||||
|
<FilterItem label="用户ID">
|
||||||
|
<el-input v-model="filters.user_id" placeholder="用户 UUID 或 *" clearable class="w-full" />
|
||||||
|
</FilterItem>
|
||||||
|
|
||||||
|
<FilterItem label="状态">
|
||||||
|
<el-select v-model="filters.status" clearable placeholder="全部" class="w-full" @change="handleFilterChange">
|
||||||
|
<el-option label="启用" value="enabled" />
|
||||||
|
<el-option label="禁用" value="disabled" />
|
||||||
|
</el-select>
|
||||||
|
</FilterItem>
|
||||||
|
|
||||||
|
<FilterItem label="API编码">
|
||||||
|
<el-input v-model="filters.api_code" placeholder="如 FLXG0V4B" clearable class="w-full" />
|
||||||
|
</FilterItem>
|
||||||
|
|
||||||
|
<FilterItem label="关键词">
|
||||||
|
<el-input
|
||||||
|
v-model="filters.keyword"
|
||||||
|
placeholder="姓名 / 备注 / 脱敏身份证"
|
||||||
|
clearable
|
||||||
|
class="w-full"
|
||||||
|
@input="handleSearch"
|
||||||
|
/>
|
||||||
|
</FilterItem>
|
||||||
|
|
||||||
|
<template #stats>
|
||||||
|
共 {{ total }} 条规则
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #buttons>
|
||||||
|
<el-button @click="resetFilters">重置</el-button>
|
||||||
|
<el-button type="primary" @click="loadEntries">查询</el-button>
|
||||||
|
</template>
|
||||||
|
</FilterSection>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #table>
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<el-table v-loading="loading" :data="entries" stripe class="w-full">
|
||||||
|
<el-table-column label="生效范围" width="110">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag :type="row.is_global ? 'warning' : 'primary'" size="small">
|
||||||
|
{{ row.is_global ? '全局' : '指定用户' }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="user_id" label="用户ID" min-width="140" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="font-mono text-xs">{{ row.is_global ? '*' : row.user_id }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="name" label="姓名" width="100">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span>{{ row.name === '*' ? '任意(*)' : row.name }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="id_card_masked" label="身份证" width="180">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span class="font-mono">{{ row.id_card_masked }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="生效接口" min-width="160">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<template v-if="row.api_codes?.includes('*')">
|
||||||
|
<el-tag size="small" type="info">全部接口</el-tag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<el-tag v-for="code in row.api_codes" :key="code" size="small" class="mr-1 mb-1">{{ code }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="status" label="状态" width="90">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-switch
|
||||||
|
:model-value="row.status === 'enabled'"
|
||||||
|
active-text="启用"
|
||||||
|
inactive-text="禁用"
|
||||||
|
inline-prompt
|
||||||
|
@change="(val) => handleToggleStatus(row, val)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="remark" label="备注" min-width="120" show-overflow-tooltip />
|
||||||
|
|
||||||
|
<el-table-column prop="created_at" label="创建时间" width="170">
|
||||||
|
<template #default="{ row }">{{ formatDate(row.created_at) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
|
<el-button type="danger" link size="small" @click="handleDelete(row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #pagination>
|
||||||
|
<el-pagination
|
||||||
|
v-if="total > 0"
|
||||||
|
v-model:current-page="pagination.page"
|
||||||
|
v-model:page-size="pagination.pageSize"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #extra>
|
||||||
|
<QueryWhitelistFormDialog
|
||||||
|
v-model="showFormDialog"
|
||||||
|
:entry="currentEntry"
|
||||||
|
@success="loadEntries"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</ListPageLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { queryWhitelistApi } from '@/api/queryWhitelist.js'
|
||||||
|
import FilterItem from '@/components/common/FilterItem.vue'
|
||||||
|
import FilterSection from '@/components/common/FilterSection.vue'
|
||||||
|
import ListPageLayout from '@/components/common/ListPageLayout.vue'
|
||||||
|
import { PlusIcon } from '@heroicons/vue/24/outline'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
|
import QueryWhitelistFormDialog from './components/QueryWhitelistFormDialog.vue'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const importing = ref(false)
|
||||||
|
const entries = ref([])
|
||||||
|
const total = ref(0)
|
||||||
|
const showFormDialog = ref(false)
|
||||||
|
const currentEntry = ref(null)
|
||||||
|
|
||||||
|
const filters = reactive({
|
||||||
|
scope: '',
|
||||||
|
user_id: '',
|
||||||
|
status: '',
|
||||||
|
api_code: '',
|
||||||
|
keyword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const pagination = reactive({
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20
|
||||||
|
})
|
||||||
|
|
||||||
|
let searchTimer = null
|
||||||
|
|
||||||
|
const buildParams = () => {
|
||||||
|
const params = {
|
||||||
|
page: pagination.page,
|
||||||
|
page_size: pagination.pageSize
|
||||||
|
}
|
||||||
|
if (filters.status) params.status = filters.status
|
||||||
|
if (filters.api_code) params.api_code = filters.api_code
|
||||||
|
if (filters.keyword) params.keyword = filters.keyword
|
||||||
|
if (filters.scope === 'global') {
|
||||||
|
params.user_id = '*'
|
||||||
|
} else if (filters.scope === 'user') {
|
||||||
|
params.user_id = filters.user_id || undefined
|
||||||
|
if (!filters.user_id) {
|
||||||
|
// 指定用户但未填 ID 时,排除全局需在服务端支持;这里用非 * 过滤需填 user_id
|
||||||
|
}
|
||||||
|
} else if (filters.user_id) {
|
||||||
|
params.user_id = filters.user_id
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadEntries = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await queryWhitelistApi.getEntries(buildParams())
|
||||||
|
entries.value = res.data?.items || []
|
||||||
|
total.value = res.data?.total || 0
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
ElMessage.error('加载查询白名单失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFilterChange = () => {
|
||||||
|
pagination.page = 1
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
if (searchTimer) clearTimeout(searchTimer)
|
||||||
|
searchTimer = setTimeout(() => {
|
||||||
|
pagination.page = 1
|
||||||
|
loadEntries()
|
||||||
|
}, 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetFilters = () => {
|
||||||
|
Object.assign(filters, { scope: '', user_id: '', status: '', api_code: '', keyword: '' })
|
||||||
|
pagination.page = 1
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
pagination.pageSize = size
|
||||||
|
pagination.page = 1
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = (page) => {
|
||||||
|
pagination.page = page
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCreate = () => {
|
||||||
|
currentEntry.value = null
|
||||||
|
showFormDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = (row) => {
|
||||||
|
currentEntry.value = { ...row }
|
||||||
|
showFormDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleToggleStatus = async (row, enabled) => {
|
||||||
|
const status = enabled ? 'enabled' : 'disabled'
|
||||||
|
try {
|
||||||
|
await queryWhitelistApi.updateEntryStatus(row.id, status)
|
||||||
|
row.status = status
|
||||||
|
ElMessage.success(enabled ? '已启用' : '已禁用')
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.error(e.message || '状态更新失败')
|
||||||
|
loadEntries()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (row) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
`确定删除该规则?(${row.id_card_masked} / ${row.name === '*' ? '任意姓名' : row.name})`,
|
||||||
|
'确认删除',
|
||||||
|
{ type: 'warning' }
|
||||||
|
)
|
||||||
|
await queryWhitelistApi.deleteEntry(row.id)
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadEntries()
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') ElMessage.error(e.message || '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleImportLegacy = async () => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(
|
||||||
|
'将导入原 processor 中 17 条硬编码身份证为「全局规则」(user_id=*,name=*,全部接口)。已存在的会自动跳过。',
|
||||||
|
'导入历史硬编码',
|
||||||
|
{ type: 'info', confirmButtonText: '开始导入' }
|
||||||
|
)
|
||||||
|
importing.value = true
|
||||||
|
const res = await queryWhitelistApi.importLegacy()
|
||||||
|
const { imported, skipped, total: t } = res.data || {}
|
||||||
|
ElMessage.success(`导入完成:新增 ${imported} 条,跳过 ${skipped} 条,共 ${t} 条`)
|
||||||
|
loadEntries()
|
||||||
|
} catch (e) {
|
||||||
|
if (e !== 'cancel') ElMessage.error(e.message || '导入失败')
|
||||||
|
} finally {
|
||||||
|
importing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateString) => {
|
||||||
|
if (!dateString) return '-'
|
||||||
|
return new Date(dateString).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadEntries)
|
||||||
|
</script>
|
||||||
@@ -349,6 +349,12 @@ const routes = [
|
|||||||
name: 'AdminCertificationReviews',
|
name: 'AdminCertificationReviews',
|
||||||
component: () => import('@/pages/admin/certification-reviews/index.vue'),
|
component: () => import('@/pages/admin/certification-reviews/index.vue'),
|
||||||
meta: { title: '企业审核' }
|
meta: { title: '企业审核' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'query-whitelist',
|
||||||
|
name: 'AdminQueryWhitelist',
|
||||||
|
component: () => import('@/pages/admin/query-whitelist/index.vue'),
|
||||||
|
meta: { title: '查询白名单' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
// 本地开发时将 /api/v1 的请求代理到 8080 端口
|
// 本地开发时将 /api/v1 的请求代理到 8080 端口
|
||||||
|
|||||||
Reference in New Issue
Block a user