260 lines
7.1 KiB
Vue
260 lines
7.1 KiB
Vue
<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>
|