jcloud/dashboard/src2/components/DomainOwner.vue

547 lines
16 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>
<div class="space-y-4">
<!-- 页面头部 -->
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-medium text-gray-900">域名所有者模板</h2>
<p class="text-sm text-gray-600">管理域名注册使用的所有者信息模板</p>
</div>
<Button
@click="createDomainOwner"
variant="solid"
>
新建模板
</Button>
</div>
<!-- 搜索和筛选 -->
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-1">
<TextInput
v-model="searchQuery"
placeholder="搜索所有者姓名、单位名称..."
class="w-full"
>
<template #prefix>
<SearchIcon class="h-4 w-4 text-gray-400" />
</template>
</TextInput>
</div>
<div class="flex gap-2">
<select
v-model="selectedType"
class="rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
>
<option value="">全部类型</option>
<option value="I">个人</option>
<option value="E">企业/组织</option>
</select>
<select
v-model="selectedStatus"
class="rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
>
<option value="">全部状态</option>
<option value="1">已实名认证</option>
<option value="0">未实名认证</option>
</select>
</div>
</div>
<!-- 列表内容 -->
<div class="rounded-lg border border-gray-200 bg-white">
<!-- 表头 -->
<div class="grid grid-cols-12 gap-4 border-b border-gray-200 bg-gray-50 px-6 py-3 text-sm font-medium text-gray-700">
<div class="col-span-3">名称</div>
<div class="col-span-2">类型</div>
<div class="col-span-2">实名状态</div>
<div class="col-span-3">联系信息</div>
<div class="col-span-2">操作</div>
</div>
<!-- 加载状态 -->
<div v-if="isLoading" class="flex items-center justify-center py-12">
<Loader2Icon class="h-6 w-6 animate-spin text-gray-400" />
<span class="ml-2 text-gray-600">加载中...</span>
</div>
<!-- 错误状态 -->
<div v-else-if="error" class="flex items-center justify-center py-12">
<AlertCircleIcon class="h-6 w-6 text-red-500" />
<span class="ml-2 text-red-600">{{ error }}</span>
</div>
<!-- 空状态 -->
<div v-else-if="filteredOwners.length === 0" class="flex flex-col items-center justify-center py-12 text-gray-500">
<UsersIcon class="mb-4 h-12 w-12 text-gray-300" />
<p class="text-lg font-medium">暂无域名所有者模板</p>
<p class="text-sm">点击上方按钮创建第一个模板</p>
</div>
<!-- 数据列表 -->
<div v-else class="divide-y divide-gray-200">
<div
v-for="owner in filteredOwners"
:key="owner.name"
class="grid grid-cols-12 gap-4 px-6 py-4 hover:bg-gray-50 transition-colors cursor-pointer"
@click="viewOwner(owner)"
>
<!-- 名称列 -->
<div class="col-span-3">
<div class="font-medium text-gray-900">{{ getDisplayName(owner) }}</div>
<div class="text-sm text-gray-500">{{ owner.title || owner.name }}</div>
</div>
<!-- 类型列 -->
<div class="col-span-2 flex items-center">
<Badge :variant="owner.c_regtype === 'I' ? 'blue' : 'purple'">
{{ owner.c_regtype === 'I' ? '个人' : '企业/组织' }}
</Badge>
</div>
<!-- 实名状态列 -->
<div class="col-span-2 flex items-center">
<Badge :variant="getRealNameStatusVariant(owner.r_status)">
{{ getRealNameStatusText(owner.r_status) }}
</Badge>
</div>
<!-- 联系信息列 -->
<div class="col-span-3">
<div class="text-sm text-gray-900">{{ owner.c_em || '-' }}</div>
<div class="text-sm text-gray-500">{{ owner.c_ph || '-' }}</div>
</div>
<!-- 操作列 -->
<div class="col-span-2 flex items-center gap-2" @click.stop>
<Button
@click="viewOwner(owner)"
variant="ghost"
size="sm"
class="text-blue-600 hover:text-blue-700"
title="查看详情"
>
<EyeIcon class="h-4 w-4" />
</Button>
<Button
@click="editOwner(owner)"
variant="ghost"
size="sm"
class="text-gray-600 hover:text-gray-700"
title="编辑"
>
<EditIcon class="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div v-if="pagination.total > pagination.limit" class="flex items-center justify-between">
<div class="text-sm text-gray-700">
显示 {{ (pagination.pageno - 1) * pagination.limit + 1 }}
{{ Math.min(pagination.pageno * pagination.limit, pagination.total) }}
{{ pagination.total }} 条记录
</div>
<div class="flex gap-2">
<Button
@click="previousPage"
:disabled="pagination.pageno <= 1"
variant="outline"
size="sm"
>
上一页
</Button>
<Button
@click="nextPage"
:disabled="pagination.pageno >= pagination.pagecount"
variant="outline"
size="sm"
>
下一页
</Button>
</div>
</div>
<!-- 创建域名所有者弹窗 -->
<DomainOwnerDialog
:visible="showCreateDialog"
:isLoading="createLoading"
@close="handleCloseDialog"
@submit="handleCreateSubmit"
/>
<!-- 详情查看弹窗 -->
<div v-if="showDetailDialog" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-2xl max-w-2xl w-full mx-4 max-h-[90vh] flex flex-col overflow-hidden">
<!-- 标题栏 -->
<div class="p-4 border-b border-gray-200 bg-white rounded-t-lg flex-shrink-0">
<div class="flex items-center justify-between">
<h3 class="text-lg font-medium text-gray-900">域名所有者详情</h3>
<button
@click="closeDetailDialog"
class="text-gray-400 hover:text-gray-600 transition-colors duration-200"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
<!-- 内容区域 -->
<div class="p-6 overflow-y-auto flex-1" v-if="selectedOwner">
<div class="space-y-6">
<!-- 基本信息 -->
<div>
<h4 class="text-base font-medium text-gray-900 mb-4">基本信息</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">所有者类型</label>
<p class="mt-1 text-sm text-gray-900">
<Badge :variant="selectedOwner.c_regtype === 'I' ? 'blue' : 'purple'">
{{ selectedOwner.c_regtype === 'I' ? '个人' : '企业/组织' }}
</Badge>
</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">实名认证状态</label>
<p class="mt-1 text-sm text-gray-900">
<Badge :variant="getRealNameStatusVariant(selectedOwner.r_status)">
{{ getRealNameStatusText(selectedOwner.r_status) }}
</Badge>
</p>
</div>
<div v-if="selectedOwner.c_regtype === 'E'">
<label class="block text-sm font-medium text-gray-700">单位名称</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_org_m || '-' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">姓名</label>
<p class="mt-1 text-sm text-gray-900">{{ getDisplayName(selectedOwner) }}</p>
</div>
</div>
</div>
<!-- 联系信息 -->
<div>
<h4 class="text-base font-medium text-gray-900 mb-4">联系信息</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">电子邮箱</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_em || '-' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">手机号码</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_ph || '-' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">省份</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_st_m || '-' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">城市</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_ct_m || '-' }}</p>
</div>
<div class="col-span-2">
<label class="block text-sm font-medium text-gray-700">通讯地址</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_adr_m || '-' }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">邮编</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_pc || '-' }}</p>
</div>
</div>
</div>
<!-- 证件信息 -->
<div>
<h4 class="text-base font-medium text-gray-900 mb-4">证件信息</h4>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700">证件类型</label>
<p class="mt-1 text-sm text-gray-900">{{ getCertificateTypeName(selectedOwner.c_idtype_gswl) }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">证件号码</label>
<p class="mt-1 text-sm text-gray-900">{{ selectedOwner.c_idnum_gswl || '-' }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="p-4 border-t border-gray-200 bg-white rounded-b-lg flex-shrink-0">
<div class="flex justify-end gap-3">
<Button
@click="closeDetailDialog"
variant="outline"
>
关闭
</Button>
<Button
@click="editOwner(selectedOwner)"
variant="solid"
>
编辑
</Button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { Badge, Button, TextInput, createResource } from 'jingrow-ui';
import { toast } from 'vue-sonner';
import { getToastErrorMessage } from '../utils/toast';
import { confirmDialog } from '../utils/components';
import DomainOwnerDialog from './DomainOwnerDialog.vue';
import SearchIcon from '~icons/lucide/search';
import EyeIcon from '~icons/lucide/eye';
import EditIcon from '~icons/lucide/edit';
import Loader2Icon from '~icons/lucide/loader-2';
import AlertCircleIcon from '~icons/lucide/alert-circle';
import UsersIcon from '~icons/lucide/users';
export default {
name: 'DomainOwner',
components: {
Badge,
Button,
TextInput,
SearchIcon,
EyeIcon,
EditIcon,
Loader2Icon,
AlertCircleIcon,
UsersIcon,
DomainOwnerDialog,
},
data() {
return {
owners: [],
isLoading: false,
error: null,
searchQuery: '',
selectedType: '',
selectedStatus: '',
pagination: {
pageno: 1,
limit: 20,
total: 0,
pagecount: 0
},
showCreateDialog: false,
createLoading: false,
showDetailDialog: false,
selectedOwner: null
};
},
computed: {
filteredOwners() {
let filtered = [...this.owners];
// 搜索过滤
if (this.searchQuery) {
const query = this.searchQuery.toLowerCase();
filtered = filtered.filter(owner => {
const displayName = this.getDisplayName(owner).toLowerCase();
const title = (owner.title || '').toLowerCase();
const email = (owner.c_em || '').toLowerCase();
return displayName.includes(query) || title.includes(query) || email.includes(query);
});
}
// 类型过滤
if (this.selectedType) {
filtered = filtered.filter(owner => owner.c_regtype === this.selectedType);
}
// 状态过滤
if (this.selectedStatus) {
filtered = filtered.filter(owner => String(owner.r_status) === this.selectedStatus);
}
return filtered;
}
},
methods: {
// 获取显示名称
getDisplayName(owner) {
if (owner.c_regtype === 'I') {
// 个人:优先显示 fullname没有的话显示 姓+名
if (owner.fullname) {
return owner.fullname;
}
const lastName = owner.c_ln_m || '';
const firstName = owner.c_fn_m || '';
return `${lastName}${firstName}`.trim() || owner.title || owner.name;
} else if (owner.c_regtype === 'E') {
// 企业:优先显示 c_org_m没有的话显示 title
return owner.c_org_m || owner.title || owner.name;
}
return owner.title || owner.name;
},
// 获取实名认证状态文本
getRealNameStatusText(status) {
const statusMap = {
'1': '已认证',
'0': '未认证',
'2': '认证中',
'3': '认证失败',
'4': '认证过期',
'5': '待审核'
};
return statusMap[String(status)] || '未知';
},
// 获取实名认证状态样式
getRealNameStatusVariant(status) {
const variantMap = {
'1': 'success', // 已认证 - 绿色
'0': 'warning', // 未认证 - 橙色
'2': 'blue', // 认证中 - 蓝色
'3': 'danger', // 认证失败 - 红色
'4': 'danger', // 认证过期 - 红色
'5': 'blue' // 待审核 - 蓝色
};
return variantMap[String(status)] || 'default';
},
// 获取域名所有者列表
async fetchOwners() {
this.isLoading = true;
this.error = null;
try {
const ownersRequest = createResource({
url: 'jcloud.api.domain_west.get_domain_owners',
onSuccess: (response) => {
if (response.status === 'Success') {
this.owners = response.data || [];
this.pagination.total = this.owners.length;
this.pagination.pagecount = Math.ceil(this.pagination.total / this.pagination.limit);
} else {
this.error = response.message || '获取域名所有者列表失败';
}
this.isLoading = false;
},
onError: (error) => {
this.error = getToastErrorMessage(error);
this.isLoading = false;
}
});
ownersRequest.submit();
} catch (error) {
this.error = '获取域名所有者列表失败';
this.isLoading = false;
}
},
// 创建新的域名所有者
createDomainOwner() {
this.showCreateDialog = true;
},
// 处理弹窗关闭
handleCloseDialog() {
this.showCreateDialog = false;
this.createLoading = false;
},
// 处理创建提交
async handleCreateSubmit(data) {
this.createLoading = true;
try {
const createRequest = createResource({
url: 'jcloud.api.domain_west.create_domain_owner',
params: data,
onSuccess: (response) => {
if (response.status === 'Success') {
toast.success('域名所有者模板创建成功!');
this.fetchOwners(); // 刷新列表
this.handleCloseDialog(); // 关闭弹窗
} else {
toast.error(response.message || '创建失败');
this.createLoading = false;
}
},
onError: (error) => {
toast.error(getToastErrorMessage(error));
this.createLoading = false;
}
});
createRequest.submit();
} catch (error) {
toast.error('创建失败,请稍后重试');
this.createLoading = false;
}
},
// 查看域名所有者详情
viewOwner(owner) {
this.selectedOwner = owner;
this.showDetailDialog = true;
},
// 关闭详情弹窗
closeDetailDialog() {
this.showDetailDialog = false;
this.selectedOwner = null;
},
// 编辑域名所有者
editOwner(owner) {
// TODO: 实现编辑功能
console.log('编辑所有者:', owner);
// 关闭详情弹窗(如果是从详情弹窗点击编辑)
if (this.showDetailDialog) {
this.closeDetailDialog();
}
},
// 获取证件类型名称
getCertificateTypeName(type) {
const typeMap = {
'SFZ': '身份证',
'HZ': '护照',
'YYZZ': '营业执照',
'ORG': '组织机构代码证'
};
return typeMap[type] || type || '-';
},
// 上一页
previousPage() {
if (this.pagination.pageno > 1) {
this.pagination.pageno--;
}
},
// 下一页
nextPage() {
if (this.pagination.pageno < this.pagination.pagecount) {
this.pagination.pageno++;
}
},
// 刷新列表
refresh() {
this.fetchOwners();
}
},
mounted() {
// 组件挂载时获取数据
this.fetchOwners();
}
};
</script>