This commit is contained in:
2026-06-18 21:16:07 +08:00
parent 9ef9ab4057
commit 33727f8d55
8 changed files with 5885 additions and 2 deletions

View File

@@ -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>