Files
tyapi-frontend/src/pages/admin/query-whitelist/components/QueryWhitelistFormDialog.vue
2026-06-18 21:16:07 +08:00

260 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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