域名详情页实名认证检测通过

This commit is contained in:
jingrow 2025-08-03 15:29:33 +08:00
parent 81d623b653
commit b8ae5c7b3d
4 changed files with 1069 additions and 1 deletions

View File

@ -9,7 +9,49 @@
<div class="rounded-md border">
<div class="p-5">
<div class="flex h-full flex-col sm:flex-row sm:items-center sm:justify-between">
<div class="mb-4 sm:mb-0">
<div class="mb-4 sm:mb-0">
<!-- 实名认证状态 -->
<div class="mb-4 p-4 bg-gradient-to-r from-gray-50 to-blue-50 rounded-xl border border-gray-200">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<div v-if="realNameStatus === 'verified'" class="flex items-center space-x-3">
<div class="inline-flex items-center rounded-full bg-gradient-to-r from-green-50 to-emerald-50 px-4 py-2 text-sm font-semibold text-green-800 border border-green-200 shadow-sm">
<svg class="mr-2 h-4 w-4 text-green-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
实名认证
</div>
<button
@click="showRealNameInfo"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-blue-700 bg-blue-100 hover:bg-blue-200 rounded-lg transition-colors duration-200 border border-blue-300"
>
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
查看认证信息
</button>
</div>
<div v-else class="flex items-center space-x-3">
<div class="inline-flex items-center rounded-full bg-gradient-to-r from-amber-50 to-orange-50 px-4 py-2 text-sm font-semibold text-amber-800 border border-amber-200 shadow-sm">
<svg class="mr-2 h-4 w-4 text-amber-600" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
</svg>
待实名认证
</div>
<button
@click="showUploadRealName"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 rounded-lg transition-all duration-200 shadow-sm hover:shadow-md"
>
<svg class="mr-1.5 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
</svg>
上传实名资料
</button>
</div>
</div>
</div>
</div>
<div v-if="$domain.pg.end_date" class="mt-2 inline-flex items-center rounded-full bg-amber-50 px-4 py-2 text-sm font-medium text-amber-800">
<ClockIcon class="mr-1.5 h-4 w-4 text-amber-500" />
到期时间{{ $format.date($domain.pg.end_date) }}
@ -158,6 +200,8 @@ export default {
autoRenewLoading: false,
whoisProtectionLoading: false,
domainOwner: null,
realNameStatus: 'unverified', // verified, unverified
realNameInfo: null, //
};
},
methods: {
@ -196,6 +240,7 @@ export default {
async getDomainOwner() {
if (!this.$domain.pg?.domain_owner) {
this.domainOwner = null;
this.realNameStatus = 'unverified';
return;
}
@ -208,17 +253,33 @@ export default {
onSuccess: (response) => {
if (response && response.status === "Success" && response.data) {
this.domainOwner = response.data;
// r_status
// r_statuscheck10
const rStatus = response.data.r_status;
// check
const isVerified = Boolean(rStatus && (rStatus === 1 || rStatus === '1' || rStatus === true));
if (isVerified) {
this.realNameStatus = 'verified';
} else {
this.realNameStatus = 'unverified';
}
} else {
this.realNameStatus = 'unverified';
}
},
onError: (error) => {
console.error('获取域名所有者信息失败:', error);
this.domainOwner = null;
this.realNameStatus = 'unverified';
}
});
getOwnerRequest.submit();
} catch (error) {
console.error('获取域名所有者信息失败:', error);
this.domainOwner = null;
this.realNameStatus = 'unverified';
}
},
renewDomain() {
@ -323,6 +384,58 @@ export default {
toast.success('域名续费成功!');
this.$domain.reload();
},
//
showRealNameInfo() {
const JsiteDomainRealNameInfoDialog = defineAsyncComponent(() => import('./JsiteDomainRealNameInfoDialog.vue'));
renderDialog(h(JsiteDomainRealNameInfoDialog, {
domain: this.domain,
domainDoc: this.$domain.pg,
realNameInfo: this.realNameInfo
}));
},
//
showUploadRealName() {
const JsiteDomainUploadRealNameDialog = defineAsyncComponent(() => import('./JsiteDomainUploadRealNameDialog.vue'));
renderDialog(h(JsiteDomainUploadRealNameDialog, {
domain: this.domain,
domainDoc: this.$domain.pg,
onSuccess: this.onUploadSuccess
}));
},
//
onUploadSuccess() {
toast.success('实名资料上传成功!');
this.getDomainOwner(); //
this.getRealNameInfo(); //
},
//
async getRealNameInfo() {
if (!this.$domain.pg?.domain) {
return;
}
try {
const getRealNameRequest = createResource({
url: 'jcloud.api.domain_west.get_west_domain_real_info',
params: {
domain: this.$domain.pg.domain
},
onSuccess: (response) => {
if (response && response.status === "success" && response.data) {
this.realNameInfo = response.data;
}
},
onError: (error) => {
console.error('获取域名实名信息失败:', error);
}
});
getRealNameRequest.submit();
} catch (error) {
console.error('获取域名实名信息失败:', error);
}
},
},
watch: {
//
@ -332,6 +445,7 @@ export default {
this.getDomainOwner();
} else {
this.domainOwner = null;
this.realNameStatus = 'unverified';
}
},
immediate: true
@ -366,7 +480,11 @@ export default {
//
if (this.$domain.pg?.domain_owner) {
this.getDomainOwner();
} else {
this.realNameStatus = 'unverified';
}
//
this.getRealNameInfo();
},
};
</script>

View File

@ -0,0 +1,318 @@
<template>
<Dialog v-bind="$attrs" :options="{ title: '实名认证信息', size: '3xl' }">
<template #body>
<div v-if="loading" class="flex justify-center items-center py-12">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span class="ml-3 text-gray-600">加载中...</span>
</div>
<div v-else-if="error" class="text-center py-12">
<div class="text-red-500 mb-4">
<AlertTriangleIcon class="h-12 w-12 mx-auto" />
</div>
<p class="text-gray-600">{{ error }}</p>
</div>
<div v-else-if="realNameData" class="space-y-6">
<!-- 认证状态 -->
<div class="bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl p-6 border border-green-200">
<div class="flex items-center">
<div class="flex-shrink-0">
<CheckCircleIcon class="h-8 w-8 text-green-500" />
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-green-900">认证已通过</h3>
<p class="text-sm text-green-700">该域名已完成实名认证</p>
</div>
</div>
</div>
<!-- 基本信息 -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<FileTextIcon class="h-5 w-5 mr-2 text-blue-600" />
域名基本信息
</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4">
<InfoItem label="域名" :value="realNameData.domain" />
<InfoItem label="注册日期" :value="formatDate(realNameData.regdate)" />
<InfoItem label="到期日期" :value="formatDate(realNameData.rexpiredate)" />
<InfoItem label="认证状态" :value="getStatusText(realNameData.c_status)" />
</div>
<div class="space-y-4">
<InfoItem label="模板ID" :value="realNameData.c_sysid" />
<InfoItem label="注册类型" :value="getRegTypeText(realNameData.c_regtype)" />
<InfoItem label="域名状态" :value="realNameData.status" />
</div>
</div>
</div>
</div>
<!-- 所有者信息 -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<UserIcon class="h-5 w-5 mr-2 text-blue-600" />
所有者信息
</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4">
<InfoItem label="中文姓名" :value="getFullChineseName()" />
<InfoItem label="英文姓名" :value="getFullEnglishName()" />
<InfoItem v-if="realNameData.owner.dom_org_m" label="中文单位" :value="realNameData.owner.dom_org_m" />
<InfoItem v-if="realNameData.owner.dom_org" label="英文单位" :value="realNameData.owner.dom_org" />
<InfoItem label="邮箱" :value="realNameData.owner.dom_em" />
</div>
<div class="space-y-4">
<InfoItem label="电话" :value="realNameData.owner.dom_ph" />
<InfoItem label="传真" :value="realNameData.owner.dom_fax" />
<InfoItem label="国家/地区" :value="realNameData.owner.dom_co" />
<InfoItem label="省份" :value="realNameData.owner.dom_st_m || realNameData.owner.dom_st" />
<InfoItem label="城市" :value="realNameData.owner.dom_ct_m || realNameData.owner.dom_ct" />
</div>
</div>
<div class="mt-4">
<InfoItem label="地址" :value="realNameData.owner.dom_adr_m || realNameData.owner.dom_adr1" />
<InfoItem label="邮编" :value="realNameData.owner.dom_pc" />
</div>
</div>
</div>
<!-- 联系人信息 -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- 管理联系人 -->
<ContactCard
title="管理联系人"
icon="UserCircleIcon"
:contact="realNameData.admin"
type="admin"
/>
<!-- 技术联系人 -->
<ContactCard
title="技术联系人"
icon="SettingsIcon"
:contact="realNameData.tech"
type="tech"
/>
<!-- 缴费联系人 -->
<ContactCard
title="缴费联系人"
icon="CreditCardIcon"
:contact="realNameData.billing"
type="bill"
/>
</div>
<!-- DNS服务器 -->
<div class="bg-white rounded-xl border border-gray-200 shadow-sm">
<div class="px-6 py-4 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-900 flex items-center">
<GlobeIcon class="h-5 w-5 mr-2 text-blue-600" />
DNS服务器
</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div v-for="(dns, index) in getDnsServers()" :key="index" class="bg-gray-50 rounded-lg p-4">
<div class="text-sm font-medium text-gray-600 mb-1">DNS{{ index + 1 }}</div>
<div class="font-mono text-sm text-gray-900">{{ dns || '未设置' }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
</Dialog>
</template>
<script>
import { Dialog, createResource } from 'jingrow-ui';
import { toast } from 'vue-sonner';
import CheckCircleIcon from '~icons/lucide/check-circle';
import AlertTriangleIcon from '~icons/lucide/alert-triangle';
import FileTextIcon from '~icons/lucide/file-text';
import UserIcon from '~icons/lucide/user';
import UserCircleIcon from '~icons/lucide/user-circle';
import SettingsIcon from '~icons/lucide/settings';
import CreditCardIcon from '~icons/lucide/credit-card';
import GlobeIcon from '~icons/lucide/globe';
//
const InfoItem = {
props: ['label', 'value'],
template: `
<div>
<dt class="text-sm font-medium text-gray-600">{{ label }}</dt>
<dd class="mt-1 text-sm text-gray-900">{{ value || '未填写' }}</dd>
</div>
`
};
//
const ContactCard = {
props: ['title', 'icon', 'contact', 'type'],
components: { UserCircleIcon, SettingsIcon, CreditCardIcon },
template: `
<div class="bg-white rounded-xl border border-gray-200 shadow-sm">
<div class="px-4 py-3 border-b border-gray-100">
<h4 class="text-sm font-medium text-gray-900 flex items-center">
<component :is="icon" class="h-4 w-4 mr-2 text-blue-600" />
{{ title }}
</h4>
</div>
<div class="p-4 space-y-3">
<div>
<div class="text-xs font-medium text-gray-600">姓名</div>
<div class="text-sm text-gray-900">{{ getContactName() }}</div>
</div>
<div>
<div class="text-xs font-medium text-gray-600">邮箱</div>
<div class="text-sm text-gray-900">{{ contact[type + '_em'] || '未填写' }}</div>
</div>
<div>
<div class="text-xs font-medium text-gray-600">电话</div>
<div class="text-sm text-gray-900">{{ contact[type + '_ph'] || '未填写' }}</div>
</div>
</div>
</div>
`,
methods: {
getContactName() {
const chineseName = (this.contact[this.type + '_ln_m'] || '') + (this.contact[this.type + '_fn_m'] || '');
const englishName = (this.contact[this.type + '_ln'] || '') + ' ' + (this.contact[this.type + '_fn'] || '');
return chineseName || englishName.trim() || '未填写';
}
}
};
export default {
name: 'JsiteDomainRealNameInfoDialog',
components: {
Dialog,
CheckCircleIcon,
AlertTriangleIcon,
FileTextIcon,
UserIcon,
UserCircleIcon,
SettingsIcon,
CreditCardIcon,
GlobeIcon,
InfoItem,
ContactCard
},
props: {
domain: String,
domainDoc: Object,
realNameInfo: Object
},
data() {
return {
loading: false,
error: null,
realNameData: null
};
},
methods: {
async loadRealNameInfo() {
if (this.realNameInfo) {
this.realNameData = this.realNameInfo;
return;
}
if (!this.domainDoc?.domain) {
this.error = '域名信息不存在';
return;
}
this.loading = true;
this.error = null;
try {
const getRealNameRequest = createResource({
url: 'jcloud.api.domain_west.get_west_domain_real_info',
params: {
domain: this.domainDoc.domain
},
onSuccess: (response) => {
if (response && response.status === "success" && response.data) {
this.realNameData = response.data;
} else {
this.error = '获取实名认证信息失败';
}
this.loading = false;
},
onError: (error) => {
console.error('获取域名实名信息失败:', error);
this.error = '获取实名认证信息失败';
this.loading = false;
}
});
getRealNameRequest.submit();
} catch (error) {
console.error('获取域名实名信息失败:', error);
this.error = '获取实名认证信息失败';
this.loading = false;
}
},
formatDate(dateStr) {
if (!dateStr) return '未知';
try {
const date = new Date(dateStr);
return date.toLocaleDateString('zh-CN');
} catch {
return dateStr;
}
},
getStatusText(status) {
const statusMap = {
1: '已通过',
2: '审核中',
3: '审核失败',
4: '待上传'
};
return statusMap[status] || status || '未知';
},
getRegTypeText(regType) {
const typeMap = {
'I': '个人',
'E': '企业'
};
return typeMap[regType] || regType || '未知';
},
getFullChineseName() {
if (!this.realNameData?.owner) return '未填写';
const lastName = this.realNameData.owner.dom_ln_m || '';
const firstName = this.realNameData.owner.dom_fn_m || '';
return lastName + firstName || '未填写';
},
getFullEnglishName() {
if (!this.realNameData?.owner) return '未填写';
const lastName = this.realNameData.owner.dom_ln || '';
const firstName = this.realNameData.owner.dom_fn || '';
return (lastName + ' ' + firstName).trim() || '未填写';
},
getDnsServers() {
if (!this.realNameData?.dns_hosts) return [];
return [
this.realNameData.dns_hosts.dns_host1,
this.realNameData.dns_hosts.dns_host2,
this.realNameData.dns_hosts.dns_host3,
this.realNameData.dns_hosts.dns_host4,
this.realNameData.dns_hosts.dns_host5,
this.realNameData.dns_hosts.dns_host6
].filter(Boolean);
}
},
mounted() {
this.loadRealNameInfo();
}
};
</script>

View File

@ -0,0 +1,468 @@
<template>
<Dialog v-bind="$attrs" :options="{ title: '上传实名资料', size: '2xl' }">
<template #body>
<div class="space-y-6">
<!-- 步骤指示器 -->
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl p-6 border border-blue-200">
<div class="flex items-start">
<div class="flex-shrink-0">
<InfoIcon class="h-6 w-6 text-blue-600" />
</div>
<div class="ml-4">
<h3 class="text-lg font-medium text-blue-900 mb-2">实名认证说明</h3>
<div class="text-sm text-blue-700 space-y-2">
<p> 个人用户请上传身份证正反面照片</p>
<p> 企业用户请上传营业执照和法人身份证</p>
<p> 文件格式支持JPGPNGPDF单个文件不超过5MB</p>
<p> 请确保证件信息清晰可见避免反光或模糊</p>
</div>
</div>
</div>
</div>
<form @submit.prevent="submitUpload" class="space-y-6">
<!-- 认证类型选择 -->
<div>
<label class="text-base font-medium text-gray-900 block mb-4">认证类型</label>
<div class="grid grid-cols-2 gap-4">
<label class="relative cursor-pointer">
<input
type="radio"
v-model="formData.certType"
value="personal"
class="sr-only peer"
/>
<div class="border-2 border-gray-200 rounded-xl p-4 peer-checked:border-blue-500 peer-checked:bg-blue-50 transition-all duration-200 hover:border-gray-300">
<div class="flex items-center">
<UserIcon class="h-6 w-6 text-gray-600 mr-3" />
<div>
<div class="font-medium text-gray-900">个人认证</div>
<div class="text-sm text-gray-600">使用个人身份证进行认证</div>
</div>
</div>
</div>
</label>
<label class="relative cursor-pointer">
<input
type="radio"
v-model="formData.certType"
value="enterprise"
class="sr-only peer"
/>
<div class="border-2 border-gray-200 rounded-xl p-4 peer-checked:border-blue-500 peer-checked:bg-blue-50 transition-all duration-200 hover:border-gray-300">
<div class="flex items-center">
<BuildingIcon class="h-6 w-6 text-gray-600 mr-3" />
<div>
<div class="font-medium text-gray-900">企业认证</div>
<div class="text-sm text-gray-600">使用营业执照进行认证</div>
</div>
</div>
</div>
</label>
</div>
</div>
<!-- 个人认证文件上传 -->
<div v-if="formData.certType === 'personal'" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 身份证正面 -->
<FileUpload
label="身份证正面"
description="请上传身份证正面照片"
:files="formData.idCardFront"
@update:files="formData.idCardFront = $event"
accept="image/*,.pdf"
:maxSize="5"
required
/>
<!-- 身份证反面 -->
<FileUpload
label="身份证反面"
description="请上传身份证反面照片"
:files="formData.idCardBack"
@update:files="formData.idCardBack = $event"
accept="image/*,.pdf"
:maxSize="5"
required
/>
</div>
</div>
<!-- 企业认证文件上传 -->
<div v-if="formData.certType === 'enterprise'" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- 营业执照 -->
<FileUpload
label="营业执照"
description="请上传营业执照照片或扫描件"
:files="formData.businessLicense"
@update:files="formData.businessLicense = $event"
accept="image/*,.pdf"
:maxSize="5"
required
/>
<!-- 法人身份证 -->
<FileUpload
label="法人身份证"
description="请上传法人身份证正面照片"
:files="formData.legalIdCard"
@update:files="formData.legalIdCard = $event"
accept="image/*,.pdf"
:maxSize="5"
required
/>
</div>
</div>
<!-- 联系信息 -->
<div class="bg-gray-50 rounded-xl p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">联系信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">联系人姓名</label>
<input
type="text"
v-model="formData.contactName"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入联系人姓名"
required
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">联系电话</label>
<input
type="tel"
v-model="formData.contactPhone"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入联系电话"
required
/>
</div>
<div class="md:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-2">邮箱地址</label>
<input
type="email"
v-model="formData.contactEmail"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入邮箱地址"
required
/>
</div>
</div>
</div>
<!-- 备注 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">备注说明可选</label>
<textarea
v-model="formData.remark"
rows="3"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="如有特殊情况请在此说明..."
></textarea>
</div>
<!-- 提交按钮 -->
<div class="flex justify-end space-x-4 pt-6 border-t">
<Button variant="outline" @click="$emit('close')">
取消
</Button>
<Button
type="submit"
:loading="loading"
class="px-8 bg-blue-600 hover:bg-blue-700 text-white"
>
提交认证
</Button>
</div>
</form>
</div>
</template>
</Dialog>
</template>
<script>
import { Dialog, Button, createResource } from 'jingrow-ui';
import { toast } from 'vue-sonner';
import InfoIcon from '~icons/lucide/info';
import UserIcon from '~icons/lucide/user';
import BuildingIcon from '~icons/lucide/building';
import UploadIcon from '~icons/lucide/upload';
import FileIcon from '~icons/lucide/file';
import XIcon from '~icons/lucide/x';
//
const FileUpload = {
props: {
label: String,
description: String,
files: Array,
accept: String,
maxSize: Number,
required: Boolean
},
emits: ['update:files'],
components: { UploadIcon, FileIcon, XIcon },
template: `
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
{{ label }}
<span v-if="required" class="text-red-500">*</span>
</label>
<p class="text-sm text-gray-600 mb-3">{{ description }}</p>
<div class="space-y-3">
<!-- 文件上传区域 -->
<div
@click="triggerFileInput"
@dragover.prevent
@drop.prevent="handleDrop"
class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition-colors duration-200"
>
<UploadIcon class="h-8 w-8 text-gray-400 mx-auto mb-2" />
<p class="text-sm text-gray-600">点击上传或拖拽文件到此处</p>
<p class="text-xs text-gray-500 mt-1">
支持 {{ accept }} 格式最大 {{ maxSize }}MB
</p>
</div>
<!-- 已上传文件列表 -->
<div v-if="files && files.length > 0" class="space-y-2">
<div
v-for="(file, index) in files"
:key="index"
class="flex items-center justify-between bg-gray-50 rounded-lg p-3"
>
<div class="flex items-center">
<FileIcon class="h-5 w-5 text-blue-600 mr-2" />
<div>
<div class="text-sm font-medium text-gray-900">{{ file.name }}</div>
<div class="text-xs text-gray-500">{{ formatFileSize(file.size) }}</div>
</div>
</div>
<button
@click="removeFile(index)"
class="text-gray-400 hover:text-red-500 transition-colors"
>
<XIcon class="h-4 w-4" />
</button>
</div>
</div>
</div>
<input
ref="fileInput"
type="file"
:accept="accept"
multiple
class="hidden"
@change="handleFileSelect"
/>
</div>
`,
methods: {
triggerFileInput() {
this.$refs.fileInput.click();
},
handleFileSelect(event) {
const files = Array.from(event.target.files);
this.addFiles(files);
},
handleDrop(event) {
const files = Array.from(event.dataTransfer.files);
this.addFiles(files);
},
addFiles(newFiles) {
const validFiles = newFiles.filter(file => {
//
if (file.size > this.maxSize * 1024 * 1024) {
toast.error(`文件 ${file.name} 超过 ${this.maxSize}MB 限制`);
return false;
}
return true;
});
const currentFiles = this.files || [];
this.$emit('update:files', [...currentFiles, ...validFiles]);
},
removeFile(index) {
const currentFiles = [...this.files];
currentFiles.splice(index, 1);
this.$emit('update:files', currentFiles);
},
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
};
export default {
name: 'JsiteDomainUploadRealNameDialog',
components: {
Dialog,
Button,
InfoIcon,
UserIcon,
BuildingIcon,
FileUpload
},
props: {
domain: String,
domainDoc: Object
},
emits: ['success', 'close'],
data() {
return {
loading: false,
formData: {
certType: 'personal',
idCardFront: [],
idCardBack: [],
businessLicense: [],
legalIdCard: [],
contactName: '',
contactPhone: '',
contactEmail: '',
remark: ''
}
};
},
methods: {
async submitUpload() {
//
if (!this.validateForm()) {
return;
}
this.loading = true;
try {
// FormData
const formData = new FormData();
//
formData.append('domain', this.domainDoc.domain);
formData.append('certType', this.formData.certType);
formData.append('contactName', this.formData.contactName);
formData.append('contactPhone', this.formData.contactPhone);
formData.append('contactEmail', this.formData.contactEmail);
formData.append('remark', this.formData.remark);
//
if (this.formData.certType === 'personal') {
if (this.formData.idCardFront.length > 0) {
this.formData.idCardFront.forEach((file, index) => {
formData.append(`idCardFront_${index}`, file);
});
}
if (this.formData.idCardBack.length > 0) {
this.formData.idCardBack.forEach((file, index) => {
formData.append(`idCardBack_${index}`, file);
});
}
} else if (this.formData.certType === 'enterprise') {
if (this.formData.businessLicense.length > 0) {
this.formData.businessLicense.forEach((file, index) => {
formData.append(`businessLicense_${index}`, file);
});
}
if (this.formData.legalIdCard.length > 0) {
this.formData.legalIdCard.forEach((file, index) => {
formData.append(`legalIdCard_${index}`, file);
});
}
}
// API
const uploadRequest = createResource({
url: 'jcloud.api.domain_west.upload_domain_real_name_files',
method: 'POST',
params: formData,
onSuccess: (response) => {
if (response && response.status === "success") {
toast.success('实名资料上传成功,请等待审核');
this.$emit('success');
this.$emit('close');
} else {
toast.error(response.message || '上传失败,请重试');
}
this.loading = false;
},
onError: (error) => {
console.error('上传实名资料失败:', error);
toast.error('上传失败,请重试');
this.loading = false;
}
});
uploadRequest.submit();
} catch (error) {
console.error('上传实名资料失败:', error);
toast.error('上传失败,请重试');
} finally {
this.loading = false;
}
},
validateForm() {
if (!this.formData.certType) {
toast.error('请选择认证类型');
return false;
}
if (this.formData.certType === 'personal') {
if (!this.formData.idCardFront.length) {
toast.error('请上传身份证正面');
return false;
}
if (!this.formData.idCardBack.length) {
toast.error('请上传身份证反面');
return false;
}
} else if (this.formData.certType === 'enterprise') {
if (!this.formData.businessLicense.length) {
toast.error('请上传营业执照');
return false;
}
if (!this.formData.legalIdCard.length) {
toast.error('请上传法人身份证');
return false;
}
}
if (!this.formData.contactName.trim()) {
toast.error('请填写联系人姓名');
return false;
}
if (!this.formData.contactPhone.trim()) {
toast.error('请填写联系电话');
return false;
}
if (!this.formData.contactEmail.trim()) {
toast.error('请填写邮箱地址');
return false;
}
//
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(this.formData.contactEmail)) {
toast.error('请填写正确的邮箱地址');
return false;
}
return true;
}
}
};
</script>

View File

@ -417,6 +417,83 @@ class WestDomain:
}
return self._make_request('/audit/', 'POST', body_params=body_params)
def get_upload_token(self, c_sysid: str, f_type_org: str, f_code_org: str,
f_type_lxr: Optional[str] = None, f_code_lxr: Optional[str] = None) -> Dict[str, Any]:
"""
获取实名上传token
Args:
c_sysid: 模板标识
f_type_org: 证件类型,详情见附录
f_code_org: 证件号码
f_type_lxr: 联系人证件类型(企业时填写)
f_code_lxr: 联系人证件号码(企业时填写)
Returns:
API响应结果包含上传token
"""
body_params = {
'act': 'uploadwcftoken',
'c_sysid': c_sysid,
'f_type_org': f_type_org,
'f_code_org': f_code_org,
}
# 添加可选参数
if f_type_lxr:
body_params['f_type_lxr'] = f_type_lxr
if f_code_lxr:
body_params['f_code_lxr'] = f_code_lxr
return self._make_request('/audit/', 'POST', body_params=body_params)
def upload_real_name_files(self, token: str, file_org: str, file_lxr: Optional[str] = None) -> Dict[str, Any]:
"""
模板实名资料上传
Args:
token: 实名上传Token
file_org: 图片完整的base64
file_lxr: 企业联系人图片完整的base64(只有token中设置了联系人的才上传)
Returns:
API响应结果
"""
upload_url = "https://netservice.vhostgo.com/wcfservice/Service1.svc/Wcf_AuditUploadFile"
# 构建请求数据
data = {
'token': token,
'file_org': file_org,
}
# 添加可选参数
if file_lxr:
data['file_lxr'] = file_lxr
headers = {
'Content-Type': 'application/json'
}
try:
# 发送JSON格式的POST请求
jingrow.log_error("西部数码实名上传调试", f"发送POST请求URL: {upload_url}, 数据: {data}")
response = requests.post(upload_url, json=data, headers=headers, timeout=30)
response.raise_for_status()
try:
result = response.json()
except json.JSONDecodeError:
jingrow.log_error("西部数码实名上传响应解析失败", response_text=response.text)
result = {"status": "error", "message": "无法解析API响应"}
return result
except requests.exceptions.RequestException as e:
jingrow.log_error("西部数码实名上传请求失败", error=str(e), url=upload_url)
return {"status": "error", "message": f"API请求失败: {str(e)}"}
def format_date(date):
"""格式化域名到期时间"""
@ -1221,6 +1298,7 @@ def get_domain_owner(name):
# 返回所有者信息
owner_data = {
"name": domain_owner.name,
"r_status": domain_owner.r_status,
"title": domain_owner.title,
"fullname": domain_owner.fullname,
"c_regtype": domain_owner.c_regtype,
@ -1708,3 +1786,89 @@ def get_west_domain_real_info(**data):
except Exception as e:
jingrow.log_error("域名实名信息查询响应解析失败", error=str(e))
return {"status": "error", "message": "域名实名信息查询响应解析失败"}
@jingrow.whitelist()
def get_west_upload_token(**data):
"""获取西部数码实名上传token"""
client = get_west_client()
if not client:
return {"status": "error", "message": "API客户端初始化失败"}
c_sysid = data.get('c_sysid')
f_type_org = data.get('f_type_org')
f_code_org = data.get('f_code_org')
f_type_lxr = data.get('f_type_lxr')
f_code_lxr = data.get('f_code_lxr')
if not all([c_sysid, f_type_org, f_code_org]):
return {"status": "error", "message": "缺少必要参数c_sysid, f_type_org, f_code_org"}
response = client.get_upload_token(c_sysid, f_type_org, f_code_org, f_type_lxr, f_code_lxr)
if response.get("status") == "error":
return response
try:
# 检查响应格式
if response.get("result") != 200:
return {"status": "error", "message": "API查询失败"}
return {
"status": "success",
"data": {
"token": response.get("data"),
"clientid": response.get("clientid")
}
}
except Exception as e:
jingrow.log_error("获取实名上传token响应解析失败", error=str(e))
return {"status": "error", "message": "获取实名上传token响应解析失败"}
@jingrow.whitelist()
def west_upload_real_name_files(**data):
"""西部数码模板实名资料上传"""
client = get_west_client()
if not client:
return {"status": "error", "message": "API客户端初始化失败"}
token = data.get('token')
file_org = data.get('file_org')
file_lxr = data.get('file_lxr')
if not all([token, file_org]):
return {"status": "error", "message": "缺少必要参数token, file_org"}
response = client.upload_real_name_files(token, file_org, file_lxr)
if response.get("status") == "error":
return response
try:
# 解析响应格式
d = response.get("d", {})
result = d.get("Result")
msg = d.get("Msg", "")
if result == 200:
return {
"status": "success",
"message": "实名资料上传成功",
"data": {
"result": result,
"msg": msg
}
}
else:
return {
"status": "error",
"message": f"实名资料上传失败: {msg}"
}
except Exception as e:
jingrow.log_error("实名资料上传响应解析失败", error=str(e))
return {"status": "error", "message": "实名资料上传响应解析失败"}