增加自定义绑定域名
This commit is contained in:
@@ -83,6 +83,32 @@ export function applyTenantDomain(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 绑定自有域名(提交后需按提示配置解析,再调 checkCustomDns)
|
||||
export function bindCustomDomain(data) {
|
||||
return request({
|
||||
url: '/platform/domain/tenant/bindCustom',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 检测自有域名解析,通过后后端自动申请 HTTPS 证书
|
||||
export function checkCustomDomainDns(id) {
|
||||
return request({
|
||||
url: '/platform/domain/tenant/checkCustomDns',
|
||||
method: 'post',
|
||||
data: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取自有域名解析配置指引(CNAME 目标 / A 记录 IP)
|
||||
export function getCustomDomainGuide() {
|
||||
return request({
|
||||
url: '/platform/domain/tenant/customDomainGuide',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 审核租户域名
|
||||
export function auditTenantDomain(data) {
|
||||
return request({
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
<div class="search-box">
|
||||
<el-input
|
||||
v-model="searchForm.sub_domain"
|
||||
placeholder="搜索二级域名"
|
||||
placeholder="搜索二级前缀或完整域名"
|
||||
clearable
|
||||
style="width: 200px; margin-right: 10px;"
|
||||
style="width: 220px; margin-right: 10px;"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
<el-select v-model="searchForm.domain_type" placeholder="类型" clearable style="width: 150px; margin-right: 10px;">
|
||||
<el-option label="平台二级域名" :value="1" />
|
||||
<el-option label="自有域名" :value="2" />
|
||||
</el-select>
|
||||
<el-select v-model="searchForm.status" placeholder="状态" clearable style="width: 120px; margin-right: 10px;">
|
||||
<el-option label="审核中" :value="0" />
|
||||
<el-option label="待生效/审核中" :value="0" />
|
||||
<el-option label="已生效" :value="1" />
|
||||
<el-option label="已禁用" :value="2" />
|
||||
</el-select>
|
||||
@@ -37,20 +41,63 @@
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="tenant_name" label="租户名称" min-width="150" />
|
||||
<el-table-column prop="sub_domain" label="二级域名前缀" width="150" />
|
||||
<el-table-column prop="main_domain" label="主域名" min-width="150" />
|
||||
<el-table-column prop="full_domain" label="完整域名" min-width="200" />
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<el-table-column label="类型" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === 0" type="warning">审核中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 1" type="success">已生效</el-tag>
|
||||
<el-tag v-else type="danger">已禁用</el-tag>
|
||||
<el-tag v-if="isCustom(scope.row)" type="warning">自有域名</el-tag>
|
||||
<el-tag v-else type="info">平台二级域名</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="180" />
|
||||
<el-table-column label="操作" width="200" fixed="right" align="center">
|
||||
<el-table-column prop="full_domain" label="完整域名" min-width="200" />
|
||||
<el-table-column prop="status" label="状态" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<template v-if="scope.row.status === 0">
|
||||
<el-tag v-if="scope.row.status === 1" type="success">已生效</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 2" type="danger">已禁用</el-tag>
|
||||
<el-tag v-else type="warning">{{ isCustom(scope.row) ? '待生效' : '审核中' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="解析检测" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="!isCustom(scope.row)" class="text-muted">—</span>
|
||||
<el-tooltip v-else-if="scope.row.verify_msg" :content="scope.row.verify_msg" placement="top">
|
||||
<el-tag :type="verifyTagType(scope.row.verify_status)">{{ verifyText(scope.row.verify_status) }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else :type="verifyTagType(scope.row.verify_status)">{{ verifyText(scope.row.verify_status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="HTTPS 证书" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="!isCustom(scope.row)" class="text-muted">平台证书</span>
|
||||
<el-tooltip v-else-if="scope.row.ssl_status === 3 && scope.row.ssl_error" :content="scope.row.ssl_error" placement="top">
|
||||
<el-tag type="danger">签发失败</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tooltip v-else-if="scope.row.ssl_status === 2 && scope.row.ssl_expire_at" :content="'到期时间:' + scope.row.ssl_expire_at" placement="top">
|
||||
<el-tag type="success">已签发</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else :type="sslTagType(scope.row.ssl_status)">{{ sslText(scope.row.ssl_status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="170" />
|
||||
<el-table-column label="操作" width="230" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<!-- 自有域名无人工审核:解析与证书就绪后自动生效,这里只提供重新检测与禁用 -->
|
||||
<template v-if="isCustom(scope.row)">
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
:loading="checkingId === scope.row.id"
|
||||
@click="handleCheckDns(scope.row)"
|
||||
>
|
||||
检测解析
|
||||
</el-button>
|
||||
<el-button v-if="scope.row.status === 1" size="small" text type="danger" @click="handleDisable(scope.row)">
|
||||
禁用
|
||||
</el-button>
|
||||
<el-button v-else-if="scope.row.status === 2" size="small" text type="success" @click="handleEnable(scope.row)">
|
||||
启用
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else-if="scope.row.status === 0">
|
||||
<el-button size="small" type="success" @click="handleAudit(scope.row, 'approve')">
|
||||
通过
|
||||
</el-button>
|
||||
@@ -87,13 +134,23 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Refresh } from '@element-plus/icons-vue'
|
||||
import { getTenantDomainList, auditTenantDomain, toggleTenantDomainStatus } from '@/api/domain'
|
||||
import {
|
||||
getTenantDomainList,
|
||||
auditTenantDomain,
|
||||
toggleTenantDomainStatus,
|
||||
checkCustomDomainDns
|
||||
} from '@/api/domain'
|
||||
|
||||
// 域名类型:与后端 models.DomainTypeCustom 对应
|
||||
const DOMAIN_TYPE_CUSTOM = 2
|
||||
|
||||
const loading = ref(false)
|
||||
const checkingId = ref<number | null>(null)
|
||||
|
||||
const searchForm = reactive({
|
||||
sub_domain: '',
|
||||
status: ''
|
||||
status: '',
|
||||
domain_type: ''
|
||||
})
|
||||
|
||||
const pagination = reactive({
|
||||
@@ -104,6 +161,20 @@ const pagination = reactive({
|
||||
|
||||
const tableData = ref<any[]>([])
|
||||
|
||||
const isCustom = (row: any) => row.domain_type === DOMAIN_TYPE_CUSTOM
|
||||
|
||||
const verifyText = (status: number) =>
|
||||
status === 1 ? '已通过' : status === 2 ? '未通过' : '未检测'
|
||||
|
||||
const verifyTagType = (status: number) =>
|
||||
status === 1 ? 'success' : status === 2 ? 'danger' : 'info'
|
||||
|
||||
const sslText = (status: number) =>
|
||||
status === 2 ? '已签发' : status === 1 ? '签发中' : status === 3 ? '签发失败' : '待处理'
|
||||
|
||||
const sslTagType = (status: number) =>
|
||||
status === 2 ? 'success' : status === 1 ? 'warning' : status === 3 ? 'danger' : 'info'
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -129,7 +200,7 @@ const handleSearch = () => {
|
||||
const handleAudit = async (row: any, action: string) => {
|
||||
const msg = action === 'approve' ? '确定要通过该域名申请吗?' : '确定要拒绝该域名申请吗?'
|
||||
await ElMessageBox.confirm(msg, '提示', { type: 'warning' })
|
||||
|
||||
|
||||
const res = await auditTenantDomain({ id: row.id, action })
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg)
|
||||
@@ -140,8 +211,8 @@ const handleAudit = async (row: any, action: string) => {
|
||||
}
|
||||
|
||||
const handleDisable = async (row: any) => {
|
||||
await ElMessageBox.confirm('确定要禁用该域名吗?', '提示', { type: 'warning' })
|
||||
|
||||
await ElMessageBox.confirm('确定要禁用该域名吗?禁用后该域名将无法访问官网。', '提示', { type: 'warning' })
|
||||
|
||||
const res = await toggleTenantDomainStatus(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg)
|
||||
@@ -151,6 +222,39 @@ const handleDisable = async (row: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleEnable = async (row: any) => {
|
||||
await ElMessageBox.confirm('确定要重新启用该域名吗?', '提示', { type: 'warning' })
|
||||
|
||||
const res = await toggleTenantDomainStatus(row.id)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg)
|
||||
fetchData()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 自有域名:重新检测解析,通过后后端自动申请证书
|
||||
const handleCheckDns = async (row: any) => {
|
||||
checkingId.value = row.id
|
||||
try {
|
||||
const res = await checkCustomDomainDns(row.id)
|
||||
if (res.code === 200) {
|
||||
const data = res.data || {}
|
||||
if (data.verify_status === 1) {
|
||||
ElMessage.success(data.ssl_message || data.verify_msg || '解析检测通过')
|
||||
} else {
|
||||
ElMessage.warning(data.verify_msg || '解析尚未生效')
|
||||
}
|
||||
fetchData()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '检测失败')
|
||||
}
|
||||
} finally {
|
||||
checkingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
})
|
||||
@@ -177,6 +281,10 @@ onMounted(() => {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.pagination-wrap {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
<template>
|
||||
<div class="container-box">
|
||||
<div class="header-bar">
|
||||
<h2>我的域名</h2>
|
||||
<el-button type="primary" @click="dialogVisible = true">
|
||||
<el-icon><Plus /></el-icon>
|
||||
申请二级域名
|
||||
</el-button>
|
||||
<h2>租户域名</h2>
|
||||
<div class="header-actions">
|
||||
<el-select
|
||||
v-model="currentTid"
|
||||
placeholder="请选择租户"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@change="fetchDomains"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tenantOptions"
|
||||
:key="item.id"
|
||||
:label="item.tenant_name || item.tenant_short_name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
<el-button type="primary" :disabled="!currentTid" @click="applyDialogVisible = true">
|
||||
<el-icon><Plus /></el-icon>
|
||||
申请二级域名
|
||||
</el-button>
|
||||
<el-button type="success" :disabled="!currentTid" @click="openBindDialog">
|
||||
<el-icon><Link /></el-icon>
|
||||
绑定自有域名
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<!-- 我的域名列表 -->
|
||||
<el-empty v-if="!currentTid" description="请先在上方选择要管理的租户" />
|
||||
|
||||
<!-- 该租户的域名列表 -->
|
||||
<el-table
|
||||
v-else
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
border
|
||||
@@ -19,36 +43,85 @@
|
||||
element-loading-text="正在加载..."
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="sub_domain" label="二级域名前缀" width="150" />
|
||||
<el-table-column prop="main_domain" label="主域名" min-width="150" />
|
||||
<el-table-column prop="full_domain" label="完整域名" min-width="200">
|
||||
<el-table-column label="类型" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
type="primary"
|
||||
:href="'http://' + scope.row.full_domain"
|
||||
target="_blank"
|
||||
>
|
||||
<el-tag v-if="isCustom(scope.row)" type="warning">自有域名</el-tag>
|
||||
<el-tag v-else type="info">平台二级域名</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="full_domain" label="完整域名" min-width="220">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" :href="domainUrl(scope.row)" target="_blank">
|
||||
{{ scope.row.full_domain }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100" align="center">
|
||||
<el-table-column prop="status" label="状态" width="110" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status === 0" type="warning">审核中</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 1" type="success"
|
||||
>已生效</el-tag
|
||||
>
|
||||
<el-tag v-else type="danger">已禁用</el-tag>
|
||||
<el-tag v-if="scope.row.status === 1" type="success">已生效</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 2" type="danger">已禁用</el-tag>
|
||||
<el-tag v-else type="warning">{{
|
||||
isCustom(scope.row) ? "待生效" : "审核中"
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="180" />
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<el-table-column label="解析检测" width="120" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="!isCustom(scope.row)" class="text-muted">—</span>
|
||||
<el-tooltip
|
||||
v-else-if="scope.row.verify_msg"
|
||||
:content="scope.row.verify_msg"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag :type="verifyTagType(scope.row.verify_status)">
|
||||
{{ verifyText(scope.row.verify_status) }}
|
||||
</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else :type="verifyTagType(scope.row.verify_status)">
|
||||
{{ verifyText(scope.row.verify_status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="HTTPS 证书" width="130" align="center">
|
||||
<template #default="scope">
|
||||
<span v-if="!isCustom(scope.row)" class="text-muted">平台证书</span>
|
||||
<el-tooltip
|
||||
v-else-if="scope.row.ssl_status === 3 && scope.row.ssl_error"
|
||||
:content="scope.row.ssl_error"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger">签发失败</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
v-else-if="scope.row.ssl_status === 2 && scope.row.ssl_expire_at"
|
||||
:content="'到期时间:' + scope.row.ssl_expire_at"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="success">已签发</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else :type="sslTagType(scope.row.ssl_status)">
|
||||
{{ sslText(scope.row.ssl_status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" label="申请时间" width="170" />
|
||||
<el-table-column label="操作" width="230" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="isCustom(scope.row)"
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
:loading="checkingId === scope.row.id"
|
||||
@click="handleCheckDns(scope.row)"
|
||||
>
|
||||
检测解析
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
type="primary"
|
||||
@click="handleCopy(scope.row.full_domain)"
|
||||
@click="handleCopy(scope.row)"
|
||||
>
|
||||
复制
|
||||
</el-button>
|
||||
@@ -63,13 +136,17 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 申请域名弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" title="申请二级域名" width="500px">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||
<!-- 申请二级域名弹窗 -->
|
||||
<el-dialog v-model="applyDialogVisible" title="申请二级域名" width="500px">
|
||||
<el-form
|
||||
:model="applyForm"
|
||||
:rules="applyRules"
|
||||
ref="applyFormRef"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="选择主域名" prop="main_domain">
|
||||
<el-select
|
||||
v-model="form.main_domain"
|
||||
v-model="applyForm.main_domain"
|
||||
placeholder="请选择主域名"
|
||||
style="width: 100%"
|
||||
>
|
||||
@@ -82,9 +159,12 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="二级前缀" prop="sub_domain">
|
||||
<el-input v-model="form.sub_domain" placeholder="请输入二级域名前缀">
|
||||
<el-input
|
||||
v-model="applyForm.sub_domain"
|
||||
placeholder="请输入二级域名前缀"
|
||||
>
|
||||
<template #append>{{
|
||||
form.main_domain ? "." + form.main_domain : ""
|
||||
applyForm.main_domain ? "." + applyForm.main_domain : ""
|
||||
}}</template>
|
||||
</el-input>
|
||||
<div class="form-tip">
|
||||
@@ -94,50 +174,128 @@
|
||||
<el-form-item label="预览">
|
||||
<div class="domain-preview">
|
||||
{{
|
||||
form.sub_domain
|
||||
? form.sub_domain + "." + (form.main_domain || "example.com")
|
||||
applyForm.sub_domain
|
||||
? applyForm.sub_domain +
|
||||
"." +
|
||||
(applyForm.main_domain || "example.com")
|
||||
: "请填写上方信息"
|
||||
}}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading"
|
||||
<el-button @click="applyDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleApplySubmit"
|
||||
:loading="applyLoading"
|
||||
>提交申请</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 绑定自有域名弹窗 -->
|
||||
<el-dialog v-model="bindDialogVisible" title="绑定自有域名" width="620px">
|
||||
<el-form
|
||||
:model="bindForm"
|
||||
:rules="bindRules"
|
||||
ref="bindFormRef"
|
||||
label-width="90px"
|
||||
>
|
||||
<el-form-item label="域名" prop="domain">
|
||||
<el-input
|
||||
v-model="bindForm.domain"
|
||||
placeholder="请输入完整域名,如 www.example.com"
|
||||
/>
|
||||
<div class="form-tip">
|
||||
不要带 http:// 与路径。根域名(example.com)与 www
|
||||
视为两个域名,需分别绑定。
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-alert type="info" :closable="false" class="guide-box">
|
||||
<template #title>绑定后请让租户到域名服务商处添加解析</template>
|
||||
<div v-if="guide.configured" class="guide-body">
|
||||
<p v-if="guide.cname_target">
|
||||
<strong>子域名(如 www)</strong>:添加 CNAME 记录,记录值
|
||||
<code>{{ guide.cname_target }}</code>
|
||||
</p>
|
||||
<p v-if="guide.a_ips && guide.a_ips.length">
|
||||
<strong>根域名(如 example.com)</strong>:添加 A 记录,记录值
|
||||
<code>{{ guide.a_ips.join(" 或 ") }}</code>
|
||||
</p>
|
||||
<p class="guide-note">
|
||||
解析生效后点击「检测解析」,系统会自动申请 HTTPS
|
||||
证书,签发成功后域名即生效。
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="guide-body">
|
||||
平台尚未配置解析目标,请先在 app.conf 中填写
|
||||
customdomain_cname_target / customdomain_a_ips。
|
||||
</div>
|
||||
</el-alert>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="bindDialogVisible = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="handleBindSubmit"
|
||||
:loading="bindLoading"
|
||||
>确认绑定</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { Plus, Link } from "@element-plus/icons-vue";
|
||||
import {
|
||||
getMyDomains,
|
||||
applyTenantDomain,
|
||||
getEnabledDomains,
|
||||
deleteTenantDomain,
|
||||
bindCustomDomain,
|
||||
checkCustomDomainDns,
|
||||
getCustomDomainGuide,
|
||||
} from "@/api/domain";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { getTenantList } from "@/api/tenant";
|
||||
|
||||
// 域名类型:与后端 models.DomainTypeCustom 对应
|
||||
const DOMAIN_TYPE_CUSTOM = 2;
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const dialogVisible = ref(false);
|
||||
const formRef = ref();
|
||||
const applyLoading = ref(false);
|
||||
const bindLoading = ref(false);
|
||||
const checkingId = ref<number | null>(null);
|
||||
const applyDialogVisible = ref(false);
|
||||
const bindDialogVisible = ref(false);
|
||||
const applyFormRef = ref();
|
||||
const bindFormRef = ref();
|
||||
|
||||
// 平台端账号不归属租户,域名操作必须显式指定租户
|
||||
const currentTid = ref<number | null>(null);
|
||||
const tenantOptions = ref<any[]>([]);
|
||||
const tableData = ref<any[]>([]);
|
||||
const domainList = ref<any[]>([]);
|
||||
const guide = reactive<{
|
||||
cname_target: string;
|
||||
a_ips: string[];
|
||||
configured: boolean;
|
||||
}>({ cname_target: "", a_ips: [], configured: false });
|
||||
|
||||
const form = reactive({
|
||||
const applyForm = reactive({
|
||||
main_domain: "",
|
||||
sub_domain: "",
|
||||
});
|
||||
|
||||
const rules = {
|
||||
const bindForm = reactive({
|
||||
domain: "",
|
||||
});
|
||||
|
||||
const applyRules = {
|
||||
main_domain: [{ required: true, message: "请选择主域名", trigger: "change" }],
|
||||
sub_domain: [
|
||||
{ required: true, message: "请输入二级域名前缀", trigger: "blur" },
|
||||
@@ -149,16 +307,68 @@ const rules = {
|
||||
],
|
||||
};
|
||||
|
||||
const bindRules = {
|
||||
domain: [
|
||||
{ required: true, message: "请输入要绑定的域名", trigger: "blur" },
|
||||
{
|
||||
pattern: /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/,
|
||||
message: "请输入合法的完整域名,如 www.example.com",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const isCustom = (row: any) => row.domain_type === DOMAIN_TYPE_CUSTOM;
|
||||
|
||||
// 已签发证书的自有域名用 https 打开,其余用 http 避免证书告警
|
||||
const domainUrl = (row: any) => {
|
||||
const scheme = !isCustom(row) || row.ssl_status === 2 ? "https" : "http";
|
||||
return `${scheme}://${row.full_domain}`;
|
||||
};
|
||||
|
||||
const verifyText = (status: number) =>
|
||||
status === 1 ? "已通过" : status === 2 ? "未通过" : "未检测";
|
||||
|
||||
const verifyTagType = (status: number) =>
|
||||
status === 1 ? "success" : status === 2 ? "danger" : "info";
|
||||
|
||||
const sslText = (status: number) =>
|
||||
status === 2
|
||||
? "已签发"
|
||||
: status === 1
|
||||
? "签发中"
|
||||
: status === 3
|
||||
? "签发失败"
|
||||
: "待处理";
|
||||
|
||||
const sslTagType = (status: number) =>
|
||||
status === 2
|
||||
? "success"
|
||||
: status === 1
|
||||
? "warning"
|
||||
: status === 3
|
||||
? "danger"
|
||||
: "info";
|
||||
|
||||
const fetchTenants = async () => {
|
||||
const res = await getTenantList({ page: 1, pageSize: 1000 });
|
||||
if (res.code === 200) {
|
||||
tenantOptions.value = res.data?.list || res.data || [];
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDomains = async () => {
|
||||
if (!currentTid.value) {
|
||||
tableData.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
// 获取我的域名列表
|
||||
const res = await getMyDomains({ tid: authStore.user.tid });
|
||||
const res = await getMyDomains({ tid: currentTid.value });
|
||||
if (res.code === 200) {
|
||||
tableData.value = res.data || [];
|
||||
}
|
||||
|
||||
// 获取可选主域名
|
||||
const domainRes = await getEnabledDomains();
|
||||
if (domainRes.code === 200) {
|
||||
domainList.value = domainRes.data || [];
|
||||
@@ -168,37 +378,96 @@ const fetchDomains = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value.validate();
|
||||
submitLoading.value = true;
|
||||
const fetchGuide = async () => {
|
||||
const res = await getCustomDomainGuide();
|
||||
if (res.code === 200 && res.data) {
|
||||
guide.cname_target = res.data.cname_target || "";
|
||||
guide.a_ips = res.data.a_ips || [];
|
||||
guide.configured = !!res.data.configured;
|
||||
}
|
||||
};
|
||||
|
||||
const openBindDialog = async () => {
|
||||
bindForm.domain = "";
|
||||
bindDialogVisible.value = true;
|
||||
await fetchGuide();
|
||||
};
|
||||
|
||||
const handleApplySubmit = async () => {
|
||||
await applyFormRef.value.validate();
|
||||
applyLoading.value = true;
|
||||
try {
|
||||
const res = await applyTenantDomain({
|
||||
tid: authStore.user.tid,
|
||||
main_domain: form.main_domain,
|
||||
sub_domain: form.sub_domain,
|
||||
tid: currentTid.value,
|
||||
main_domain: applyForm.main_domain,
|
||||
sub_domain: applyForm.sub_domain,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg);
|
||||
dialogVisible.value = false;
|
||||
form.sub_domain = "";
|
||||
applyDialogVisible.value = false;
|
||||
applyForm.sub_domain = "";
|
||||
fetchDomains();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "申请失败");
|
||||
}
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
applyLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = (domain: string) => {
|
||||
navigator.clipboard.writeText("http://" + domain);
|
||||
const handleBindSubmit = async () => {
|
||||
await bindFormRef.value.validate();
|
||||
bindLoading.value = true;
|
||||
try {
|
||||
const res = await bindCustomDomain({
|
||||
tid: currentTid.value,
|
||||
domain: bindForm.domain,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(res.msg || "绑定成功");
|
||||
bindDialogVisible.value = false;
|
||||
bindForm.domain = "";
|
||||
fetchDomains();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "绑定失败");
|
||||
}
|
||||
} finally {
|
||||
bindLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCheckDns = async (row: any) => {
|
||||
checkingId.value = row.id;
|
||||
try {
|
||||
const res = await checkCustomDomainDns(row.id);
|
||||
if (res.code === 200) {
|
||||
const data = res.data || {};
|
||||
if (data.verify_status === 1) {
|
||||
ElMessage.success(data.ssl_message || data.verify_msg || "解析检测通过");
|
||||
} else {
|
||||
ElMessage.warning(data.verify_msg || "解析尚未生效,请稍后重试");
|
||||
}
|
||||
fetchDomains();
|
||||
} else {
|
||||
ElMessage.error(res.msg || "检测失败");
|
||||
}
|
||||
} finally {
|
||||
checkingId.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = (row: any) => {
|
||||
navigator.clipboard.writeText(domainUrl(row));
|
||||
ElMessage.success("链接已复制到剪贴板");
|
||||
};
|
||||
|
||||
const handleDelete = async (row: any) => {
|
||||
try {
|
||||
const extra = isCustom(row)
|
||||
? "删除后该域名将无法访问,已签发的证书也不再续期。"
|
||||
: "";
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除域名 "${row.full_domain}" 吗?`,
|
||||
`确定要删除域名 "${row.full_domain}" 吗?${extra}`,
|
||||
"提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
@@ -221,7 +490,8 @@ const handleDelete = async (row: any) => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchDomains();
|
||||
fetchTenants();
|
||||
fetchGuide();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -242,10 +512,17 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 5px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.domain-preview {
|
||||
@@ -255,4 +532,34 @@ onMounted(() => {
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.guide-box {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.guide-body {
|
||||
font-size: 13px;
|
||||
line-height: 1.9;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 1px 6px;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 3px;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.guide-note {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user