1340 lines
55 KiB
Vue
1340 lines
55 KiB
Vue
<template>
|
||
<div>
|
||
<!-- 页面头部 -->
|
||
<div class="sticky top-0 z-10 shrink-0">
|
||
<Header>
|
||
<Breadcrumbs
|
||
:items="[
|
||
{ label: '域名', route: '/domains' },
|
||
{ label: '新建域名', route: '/domains/new' }
|
||
]"
|
||
/>
|
||
</Header>
|
||
</div>
|
||
|
||
<!-- 主要内容区域 -->
|
||
<div class="mx-auto max-w-7xl px-5">
|
||
<!-- 第一步:选择域名和支付方式 -->
|
||
<div v-if="!showPaymentProcessing" class="space-y-12 pb-[50vh] pt-12">
|
||
<div class="mb-6">
|
||
<h1 class="text-2xl font-bold text-gray-900 mb-2">新建域名</h1>
|
||
<p class="text-sm text-gray-600">
|
||
请输入要注册的域名,系统将自动查询可用性并为您注册。
|
||
</p>
|
||
</div>
|
||
|
||
<div class="space-y-6">
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">域名查询</label>
|
||
<div class="flex flex-col sm:flex-row gap-2">
|
||
<input
|
||
v-model="domainName"
|
||
type="text"
|
||
placeholder="请输入域名关键词,如:example"
|
||
class="flex-1 border rounded px-3 py-2"
|
||
@keyup.enter="checkDomain"
|
||
/>
|
||
<div class="relative">
|
||
<button
|
||
@click="showSuffixSelector = !showSuffixSelector"
|
||
class="suffix-selector-btn px-4 py-2 border border-gray-300 bg-white text-gray-700 rounded-l-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 whitespace-nowrap flex items-center gap-2"
|
||
>
|
||
{{ selectedSuffix }}
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||
</svg>
|
||
</button>
|
||
|
||
</div>
|
||
<button
|
||
@click="checkDomain"
|
||
:disabled="isChecking"
|
||
class="px-4 py-2 bg-blue-600 text-white rounded-r-md hover:bg-blue-700 disabled:bg-gray-300 whitespace-nowrap"
|
||
>
|
||
{{ isChecking ? '查询中...' : '查询域名' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 域名后缀选择区域 -->
|
||
<div v-if="showSuffixSelector" class="border rounded-lg p-4 bg-gray-50">
|
||
<div class="mb-4">
|
||
<h3 class="text-sm font-medium text-gray-700 mb-3">选择域名后缀</h3>
|
||
|
||
<!-- 搜索和排序 -->
|
||
<div class="flex flex-col sm:flex-row gap-2 mb-4">
|
||
<div class="search-container">
|
||
<input
|
||
v-model="suffixSearch"
|
||
type="text"
|
||
placeholder="搜索后缀"
|
||
class="search-input"
|
||
/>
|
||
<svg class="search-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||
</svg>
|
||
</div>
|
||
<select v-model="suffixSort" class="px-3 py-2 text-sm border rounded-md bg-white min-w-[120px]">
|
||
<option value="default">默认排序</option>
|
||
<option value="popular">热门优先</option>
|
||
<option value="alphabetical">字母排序</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 分类标签 -->
|
||
<div class="category-tabs overflow-x-auto">
|
||
<div class="flex gap-2 min-w-max">
|
||
<button
|
||
v-for="category in suffixCategories"
|
||
:key="category.key"
|
||
@click="selectedCategory = category.key"
|
||
class="category-tab whitespace-nowrap"
|
||
:class="{ active: selectedCategory === category.key }"
|
||
>
|
||
{{ category.label }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 域名后缀网格 -->
|
||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-10 gap-2 domain-suffix-grid">
|
||
<button
|
||
v-for="suffix in filteredSuffixes"
|
||
:key="suffix.value"
|
||
@click="selectSuffix(suffix.value)"
|
||
class="domain-suffix-item p-3 text-center rounded-lg border transition-all duration-200"
|
||
:class="selectedSuffix === suffix.value
|
||
? 'border-blue-500 border-2 shadow-sm bg-blue-50'
|
||
: 'bg-white border-gray-200 hover:border-blue-300 hover:bg-gray-50'"
|
||
>
|
||
<div class="flex items-center justify-center gap-1">
|
||
<span class="text-sm font-medium" :class="selectedSuffix === suffix.value ? 'text-blue-700' : 'text-gray-800'">{{ suffix.value }}</span>
|
||
<span v-if="suffix.hot" class="hot-tag">
|
||
HOT
|
||
</span>
|
||
</div>
|
||
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 域名查询结果 -->
|
||
<div v-if="domainCheckResult" class="border rounded-lg p-4">
|
||
<div v-if="domainCheckResult.available" class="text-green-600">
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
|
||
</svg>
|
||
<span class="font-medium">域名可用</span>
|
||
</div>
|
||
<p class="text-sm text-gray-600">{{ fullDomain }} 可以注册</p>
|
||
<div v-if="domainPrice" class="mt-2 text-lg font-bold text-green-600">
|
||
¥{{ domainPrice }}/年
|
||
</div>
|
||
</div>
|
||
<div v-else class="text-red-600">
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
|
||
</svg>
|
||
<span class="font-medium">域名不可用</span>
|
||
</div>
|
||
<p class="text-sm text-gray-600">{{ fullDomain }} 已被注册</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="domainCheckResult && domainCheckResult.available">
|
||
<!-- 域名所有者选择 -->
|
||
<div class="mb-4">
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">域名所有者 *</label>
|
||
<div class="flex gap-2">
|
||
<select
|
||
v-model="selectedDomainOwner"
|
||
class="flex-1 border rounded px-3 py-2"
|
||
required
|
||
>
|
||
<option value="">请选择域名所有者</option>
|
||
<option v-for="owner in domainOwners" :key="owner.name" :value="owner.name">
|
||
{{ getOwnerDisplayName(owner) }}
|
||
</option>
|
||
</select>
|
||
<button
|
||
@click="showNewOwnerDialog = true"
|
||
type="button"
|
||
class="px-4 py-2 border border-gray-300 bg-white text-gray-700 rounded hover:bg-gray-50 flex items-center gap-2"
|
||
>
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
||
</svg>
|
||
新建域名所有者
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">购买时长</label>
|
||
<select v-model="period" class="w-full border rounded px-3 py-2">
|
||
<option v-for="p in periods" :key="p.value" :value="p.value">{{ p.label }}</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 价格信息显示 -->
|
||
<div v-if="domainCheckResult && domainCheckResult.available && domainPrice" class="border-t border-gray-200 pt-4">
|
||
<div class="flex justify-between items-center">
|
||
<div class="text-sm text-gray-600">年度费用</div>
|
||
<div class="font-medium">
|
||
¥ {{ domainPrice }}
|
||
<span class="text-gray-500 text-sm">(年付)</span>
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-between items-center mt-2">
|
||
<div class="text-sm text-gray-600">购买时长</div>
|
||
<div class="font-medium">{{ period }} 年</div>
|
||
</div>
|
||
<div class="flex justify-between items-center mt-2 text-lg font-bold">
|
||
<div>总计</div>
|
||
<div>¥ {{ getTotalAmount() }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 支付方式选择 -->
|
||
<div v-if="domainCheckResult && domainCheckResult.available" class="border-t border-gray-200 pt-4">
|
||
<label class="block text-sm font-medium text-gray-700 mb-3">
|
||
选择支付方式
|
||
</label>
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-2">
|
||
<button
|
||
class="p-4 border rounded-lg flex flex-col items-center justify-center hover:bg-gray-50 transition-all"
|
||
:class="{'border-blue-500 border-2 shadow-sm': selectedPaymentMethod === 'balance', 'border-gray-200': selectedPaymentMethod !== 'balance'}"
|
||
@click="selectedPaymentMethod = 'balance'"
|
||
>
|
||
<span class="text-gray-800 font-medium">余额支付</span>
|
||
</button>
|
||
|
||
<button
|
||
class="p-4 border rounded-lg hover:bg-gray-50 transition-all"
|
||
:class="{'border-blue-500 border-2 shadow-sm': selectedPaymentMethod === 'alipay', 'border-gray-200': selectedPaymentMethod !== 'alipay'}"
|
||
@click="selectedPaymentMethod = 'alipay'"
|
||
>
|
||
<div class="flex flex-col items-center">
|
||
<div class="mb-2">
|
||
<AlipayLogo class="h-8" />
|
||
</div>
|
||
</div>
|
||
</button>
|
||
|
||
<button
|
||
class="p-4 border rounded-lg hover:bg-gray-50 transition-all"
|
||
:class="{'border-blue-500 border-2 shadow-sm': selectedPaymentMethod === 'wechatpay', 'border-gray-200': selectedPaymentMethod !== 'wechatpay'}"
|
||
@click="selectedPaymentMethod = 'wechatpay'"
|
||
>
|
||
<div class="flex flex-col items-center">
|
||
<div class="mb-2">
|
||
<WeChatPayLogo class="h-8" />
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="error" class="p-3 bg-red-50 text-red-700 rounded-md text-sm">
|
||
{{ error }}
|
||
</div>
|
||
|
||
<!-- 注册按钮 -->
|
||
<div v-if="domainCheckResult && domainCheckResult.available" class="pt-4">
|
||
<button
|
||
type="button"
|
||
class="w-full px-4 py-2 bg-[#1fc76f] border border-transparent rounded-md text-sm font-medium text-white hover:bg-[#19b862] focus:outline-none"
|
||
@click="registerDomain"
|
||
:disabled="isLoading"
|
||
>
|
||
{{ isLoading ? '处理中...' : '注册域名' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第二步:处理支付 -->
|
||
<div v-else class="space-y-12 pb-[50vh] pt-12">
|
||
<!-- 加载中状态 -->
|
||
<div v-if="isProcessingPayment" class="text-center py-8">
|
||
<div class="h-12 w-12 mx-auto animate-spin text-blue-600 mb-4 flex items-center justify-center">
|
||
<i class="fe fe-loader text-3xl"></i>
|
||
</div>
|
||
<p class="text-gray-700 text-lg">正在处理支付,请稍候...</p>
|
||
</div>
|
||
|
||
<!-- 支付成功状态 -->
|
||
<div v-else-if="paymentSuccess" class="text-center py-8">
|
||
<div class="flex justify-center mb-6">
|
||
<div class="rounded-full bg-green-100 p-3">
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<h3 class="text-xl font-medium text-gray-900 mb-2">支付成功!</h3>
|
||
<p class="text-gray-600 mb-4">
|
||
您的订单已支付成功,域名正在注册中,注册需要1分钟左右,请耐心等待。
|
||
</p>
|
||
<p class="text-gray-500 text-sm">
|
||
您可以在域名列表中查看域名状态。
|
||
</p>
|
||
<div class="mt-6">
|
||
<button
|
||
type="button"
|
||
class="px-4 py-2 bg-[#1fc76f] border border-transparent rounded-md text-sm font-medium text-white hover:bg-[#19b862] focus:outline-none"
|
||
@click="goToDomainList"
|
||
>
|
||
返回域名列表
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 微信支付状态 -->
|
||
<div v-else-if="selectedPaymentMethod === 'wechatpay' && paymentQrCode" class="text-center">
|
||
<div class="relative mb-6">
|
||
<div class="flex items-center justify-center">
|
||
<WeChatPayLogo class="h-10" />
|
||
</div>
|
||
</div>
|
||
|
||
<h3 class="text-lg font-medium text-gray-900 mb-4">域名注册 - {{ fullDomain }}</h3>
|
||
|
||
<div class="text-center mb-6">
|
||
<div class="text-sm text-gray-600 mb-2">扫一扫付款(元)</div>
|
||
<div class="text-3xl font-bold text-[#FF0036]">{{ order?.total_amount }} 元</div>
|
||
</div>
|
||
|
||
<div class="flex justify-center">
|
||
<div class="qrcode-container bg-white p-4 border border-gray-100 shadow-sm rounded-lg">
|
||
<img
|
||
:src="paymentQrCodeImage"
|
||
alt="微信支付二维码"
|
||
class="qrcode-image"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-6 text-center">
|
||
<p class="text-gray-600">
|
||
请使用微信扫描二维码完成支付
|
||
</p>
|
||
<p class="text-gray-500 text-sm mt-2">二维码有效期 15 分钟</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 支付宝支付状态 -->
|
||
<div v-else-if="selectedPaymentMethod === 'alipay' && paymentUrl" class="text-center py-6">
|
||
<div class="mb-6">
|
||
<AlipayLogo class="h-10 mx-auto" />
|
||
</div>
|
||
|
||
<h3 class="text-lg font-medium text-gray-900 mb-4">请在新页面完成支付宝支付</h3>
|
||
<p class="text-gray-600 mb-6">
|
||
如果没有自动跳转,请点击下方按钮打开支付页面
|
||
</p>
|
||
<div class="space-y-4">
|
||
<button
|
||
class="w-full px-6 py-3 bg-[#1677FF] text-white rounded-lg hover:bg-[#0E5FD8] transition-colors shadow-sm"
|
||
@click="window.open(paymentUrl, '_blank')"
|
||
>
|
||
打开支付页面
|
||
</button>
|
||
<p class="text-gray-600 text-sm mt-4">
|
||
支付完成后,请稍等片刻,系统会自动刷新页面
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="error" class="p-3 bg-red-50 text-red-700 rounded-md text-sm">
|
||
{{ error }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新建域名所有者对话框 -->
|
||
<DomainOwnerDialog
|
||
:visible="showNewOwnerDialog"
|
||
:is-loading="isCreatingOwner"
|
||
@close="showNewOwnerDialog = false"
|
||
@submit="handleOwnerSubmit"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { toast } from 'vue-sonner';
|
||
import { DashboardError } from '../utils/error';
|
||
import AlipayLogo from '../logo/AlipayLogo.vue';
|
||
import WeChatPayLogo from '../logo/WeChatPayLogo.vue';
|
||
import DomainOwnerDialog from '../components/DomainOwnerDialog.vue';
|
||
import router from '../router';
|
||
import { getChinaRegions } from '../utils/regions';
|
||
|
||
export default {
|
||
name: 'NewJsiteDomain',
|
||
components: {
|
||
AlipayLogo,
|
||
WeChatPayLogo,
|
||
DomainOwnerDialog
|
||
},
|
||
data() {
|
||
return {
|
||
domainName: '',
|
||
selectedSuffix: '.com',
|
||
period: 1,
|
||
selectedPaymentMethod: null,
|
||
periods: [
|
||
{ value: 1, label: '1年' },
|
||
{ value: 2, label: '2年' },
|
||
{ value: 3, label: '3年' },
|
||
{ value: 5, label: '5年' },
|
||
{ value: 10, label: '10年' }
|
||
],
|
||
isLoading: false,
|
||
isChecking: false,
|
||
error: null,
|
||
domainCheckResult: null,
|
||
domainPrice: null,
|
||
// 支付相关状态
|
||
order: null,
|
||
domain: null,
|
||
showPaymentProcessing: false,
|
||
paymentSuccess: false,
|
||
isProcessingPayment: false,
|
||
paymentUrl: null,
|
||
paymentQrCode: null,
|
||
paymentQrCodeImage: null,
|
||
checkInterval: null,
|
||
// 域名后缀相关
|
||
suffixSearch: '',
|
||
suffixSort: 'default',
|
||
selectedCategory: 'popular',
|
||
showSuffixSelector: false,
|
||
// 域名所有者相关
|
||
selectedDomainOwner: '',
|
||
domainOwners: [],
|
||
showNewOwnerDialog: false,
|
||
isCreatingOwner: false,
|
||
suffixCategories: [
|
||
{ key: 'popular', label: '热门域名' },
|
||
{ key: 'gtd', label: '通用顶级域名' },
|
||
{ key: 'ntd', label: '新顶级域名' },
|
||
{ key: 'country', label: '国家/地区域名' },
|
||
{ key: 'cn', label: 'CN域名' },
|
||
{ key: 'chinese', label: '中文域名' },
|
||
{ key: 'business', label: '商业域名' },
|
||
{ key: 'tech', label: '科技域名' },
|
||
{ key: 'creative', label: '创意域名' },
|
||
{ key: 'lifestyle', label: '生活域名' },
|
||
{ key: 'all', label: '全部域名' }
|
||
],
|
||
allSuffixes: [
|
||
// 通用顶级域名 (gtd)
|
||
{ value: '.com', label: '.com', hot: true, category: 'gtd' },
|
||
{ value: '.net', label: '.net', hot: true, category: 'gtd' },
|
||
{ value: '.org', label: '.org', hot: true, category: 'gtd' },
|
||
{ value: '.info', label: '.info', hot: false, category: 'gtd' },
|
||
{ value: '.biz', label: '.biz', hot: false, category: 'gtd' },
|
||
|
||
// 新顶级域名 (ntd)
|
||
{ value: '.top', label: '.top', hot: true, category: 'ntd' },
|
||
{ value: '.xyz', label: '.xyz', hot: true, category: 'ntd' },
|
||
{ value: '.vip', label: '.vip', hot: true, category: 'ntd' },
|
||
{ value: '.site', label: '.site', hot: true, category: 'ntd' },
|
||
{ value: '.shop', label: '.shop', hot: true, category: 'ntd' },
|
||
{ value: '.club', label: '.club', hot: true, category: 'ntd' },
|
||
{ value: '.icu', label: '.icu', hot: true, category: 'ntd' },
|
||
{ value: '.online', label: '.online', hot: false, category: 'ntd' },
|
||
{ value: '.love', label: '.love', hot: false, category: 'ntd' },
|
||
{ value: '.store', label: '.store', hot: false, category: 'ntd' },
|
||
{ value: '.xin', label: '.xin', hot: false, category: 'ntd' },
|
||
{ value: '.ink', label: '.ink', hot: false, category: 'ntd' },
|
||
{ value: '.work', label: '.work', hot: false, category: 'ntd' },
|
||
{ value: '.fun', label: '.fun', hot: false, category: 'ntd' },
|
||
{ value: '.tech', label: '.tech', hot: false, category: 'ntd' },
|
||
{ value: '.art', label: '.art', hot: true, category: 'ntd' },
|
||
{ value: '.group', label: '.group', hot: false, category: 'ntd' },
|
||
{ value: '.app', label: '.app', hot: true, category: 'ntd' },
|
||
{ value: '.dev', label: '.dev', hot: true, category: 'ntd' },
|
||
{ value: '.io', label: '.io', hot: true, category: 'ntd' },
|
||
{ value: '.ai', label: '.ai', hot: true, category: 'ntd' },
|
||
{ value: '.me', label: '.me', hot: true, category: 'ntd' },
|
||
{ value: '.co', label: '.co', hot: true, category: 'ntd' },
|
||
{ value: '.town', label: '.town', hot: false, category: 'ntd' },
|
||
{ value: '.toys', label: '.toys', hot: false, category: 'ntd' },
|
||
{ value: '.trade', label: '.trade', hot: false, category: 'ntd' },
|
||
{ value: '.tips', label: '.tips', hot: false, category: 'ntd' },
|
||
{ value: '.tm', label: '.tm', hot: false, category: 'ntd' },
|
||
{ value: '.today', label: '.today', hot: false, category: 'ntd' },
|
||
{ value: '.tools', label: '.tools', hot: false, category: 'ntd' },
|
||
{ value: '.tv', label: '.tv', hot: false, category: 'ntd' },
|
||
{ value: '.tw', label: '.tw', hot: false, category: 'ntd' },
|
||
{ value: '.uno', label: '.uno', hot: false, category: 'ntd' },
|
||
{ value: '.vc', label: '.vc', hot: false, category: 'ntd' },
|
||
{ value: '.ventures', label: '.ventures', hot: false, category: 'ntd' },
|
||
{ value: '.vet', label: '.vet', hot: false, category: 'ntd' },
|
||
{ value: '.video', label: '.video', hot: false, category: 'ntd' },
|
||
{ value: '.vin', label: '.vin', hot: false, category: 'ntd' },
|
||
{ value: '.wang', label: '.wang', hot: false, category: 'ntd' },
|
||
{ value: '.watch', label: '.watch', hot: false, category: 'ntd' },
|
||
{ value: '.webcam', label: '.webcam', hot: false, category: 'ntd' },
|
||
{ value: '.website', label: '.website', hot: false, category: 'ntd' },
|
||
{ value: '.wiki', label: '.wiki', hot: false, category: 'ntd' },
|
||
{ value: '.win', label: '.win', hot: false, category: 'ntd' },
|
||
{ value: '.wine', label: '.wine', hot: false, category: 'ntd' },
|
||
{ value: '.works', label: '.works', hot: false, category: 'ntd' },
|
||
{ value: '.world', label: '.world', hot: false, category: 'ntd' },
|
||
{ value: '.wtf', label: '.wtf', hot: false, category: 'ntd' },
|
||
{ value: '.rent', label: '.rent', hot: false, category: 'ntd' },
|
||
{ value: '.review', label: '.review', hot: false, category: 'ntd' },
|
||
{ value: '.rip', label: '.rip', hot: false, category: 'ntd' },
|
||
{ value: '.run', label: '.run', hot: false, category: 'ntd' },
|
||
{ value: '.sale', label: '.sale', hot: false, category: 'ntd' },
|
||
{ value: '.school', label: '.school', hot: false, category: 'ntd' },
|
||
{ value: '.science', label: '.science', hot: false, category: 'ntd' },
|
||
{ value: '.services', label: '.services', hot: false, category: 'ntd' },
|
||
{ value: '.sex', label: '.sex', hot: false, category: 'ntd' },
|
||
{ value: '.sexy', label: '.sexy', hot: false, category: 'ntd' },
|
||
{ value: '.shopping', label: '.shopping', hot: false, category: 'ntd' },
|
||
{ value: '.show', label: '.show', hot: false, category: 'ntd' },
|
||
{ value: '.skin', label: '.skin', hot: false, category: 'ntd' },
|
||
{ value: '.social', label: '.social', hot: false, category: 'ntd' },
|
||
{ value: '.software', label: '.software', hot: false, category: 'ntd' },
|
||
{ value: '.solutions', label: '.solutions', hot: false, category: 'ntd' },
|
||
{ value: '.space', label: '.space', hot: false, category: 'ntd' },
|
||
{ value: '.studio', label: '.studio', hot: false, category: 'ntd' },
|
||
{ value: '.style', label: '.style', hot: false, category: 'ntd' },
|
||
{ value: '.support', label: '.support', hot: false, category: 'ntd' },
|
||
{ value: '.tax', label: '.tax', hot: false, category: 'ntd' },
|
||
{ value: '.team', label: '.team', hot: false, category: 'ntd' },
|
||
{ value: '.technology', label: '.technology', hot: false, category: 'ntd' },
|
||
{ value: '.organic', label: '.organic', hot: false, category: 'ntd' },
|
||
{ value: '.party', label: '.party', hot: false, category: 'ntd' },
|
||
{ value: '.pet', label: '.pet', hot: false, category: 'ntd' },
|
||
{ value: '.news', label: '.news', hot: false, category: 'ntd' },
|
||
{ value: '.ooo', label: '.ooo', hot: false, category: 'ntd' },
|
||
{ value: '.network', label: '.network', hot: false, category: 'ntd' },
|
||
{ value: '.mobi', label: '.mobi', hot: false, category: 'ntd' },
|
||
{ value: '.mom', label: '.mom', hot: false, category: 'ntd' },
|
||
{ value: '.money', label: '.money', hot: false, category: 'ntd' },
|
||
{ value: '.monster', label: '.monster', hot: false, category: 'ntd' },
|
||
{ value: '.motorcycles', label: '.motorcycles', hot: false, category: 'ntd' },
|
||
{ value: '.photo', label: '.photo', hot: false, category: 'ntd' },
|
||
{ value: '.photography', label: '.photography', hot: false, category: 'ntd' },
|
||
{ value: '.photos', label: '.photos', hot: false, category: 'ntd' },
|
||
{ value: '.pics', label: '.pics', hot: false, category: 'ntd' },
|
||
{ value: '.pink', label: '.pink', hot: false, category: 'ntd' },
|
||
{ value: '.plus', label: '.plus', hot: false, category: 'ntd' },
|
||
{ value: '.press', label: '.press', hot: false, category: 'ntd' },
|
||
{ value: '.pro', label: '.pro', hot: false, category: 'ntd' },
|
||
{ value: '.property', label: '.property', hot: false, category: 'ntd' },
|
||
{ value: '.pub', label: '.pub', hot: false, category: 'ntd' },
|
||
{ value: '.pw', label: '.pw', hot: false, category: 'ntd' },
|
||
{ value: '.qpon', label: '.qpon', hot: false, category: 'ntd' },
|
||
{ value: '.quest', label: '.quest', hot: false, category: 'ntd' },
|
||
{ value: '.racing', label: '.racing', hot: false, category: 'ntd' },
|
||
{ value: '.red', label: '.red', hot: false, category: 'ntd' },
|
||
{ value: '.ren', label: '.ren', hot: false, category: 'ntd' },
|
||
{ value: '.ing', label: '.ing', hot: false, category: 'ntd' },
|
||
{ value: '.kim', label: '.kim', hot: false, category: 'ntd' },
|
||
{ value: '.la', label: '.la', hot: false, category: 'ntd' },
|
||
{ value: '.land', label: '.land', hot: false, category: 'ntd' },
|
||
{ value: '.law', label: '.law', hot: false, category: 'ntd' },
|
||
{ value: '.lawyer', label: '.lawyer', hot: false, category: 'ntd' },
|
||
{ value: '.lc', label: '.lc', hot: false, category: 'ntd' },
|
||
{ value: '.life', label: '.life', hot: false, category: 'ntd' },
|
||
{ value: '.link', label: '.link', hot: false, category: 'ntd' },
|
||
{ value: '.live', label: '.live', hot: false, category: 'ntd' },
|
||
{ value: '.loan', label: '.loan', hot: false, category: 'ntd' },
|
||
{ value: '.lol', label: '.lol', hot: false, category: 'ntd' },
|
||
{ value: '.ltd', label: '.ltd', hot: false, category: 'ntd' },
|
||
{ value: '.luxe', label: '.luxe', hot: false, category: 'ntd' },
|
||
{ value: '.makeup', label: '.makeup', hot: false, category: 'ntd' },
|
||
{ value: '.market', label: '.market', hot: false, category: 'ntd' },
|
||
{ value: '.marketing', label: '.marketing', hot: false, category: 'ntd' },
|
||
{ value: '.mba', label: '.mba', hot: false, category: 'ntd' },
|
||
{ value: '.media', label: '.media', hot: false, category: 'ntd' },
|
||
{ value: '.men', label: '.men', hot: false, category: 'ntd' },
|
||
{ value: '.city', label: '.city', hot: false, category: 'ntd' },
|
||
{ value: '.click', label: '.click', hot: false, category: 'ntd' },
|
||
{ value: '.clothing', label: '.clothing', hot: false, category: 'ntd' },
|
||
{ value: '.cloud', label: '.cloud', hot: false, category: 'ntd' },
|
||
{ value: '.ceo', label: '.ceo', hot: false, category: 'ntd' },
|
||
{ value: '.cfd', label: '.cfd', hot: false, category: 'ntd' },
|
||
{ value: '.chat', label: '.chat', hot: false, category: 'ntd' },
|
||
{ value: '.center', label: '.center', hot: false, category: 'ntd' },
|
||
{ value: '.cards', label: '.cards', hot: false, category: 'ntd' },
|
||
{ value: '.cash', label: '.cash', hot: false, category: 'ntd' },
|
||
{ value: '.cab', label: '.cab', hot: false, category: 'ntd' },
|
||
{ value: '.cafe', label: '.cafe', hot: false, category: 'ntd' },
|
||
{ value: '.camera', label: '.camera', hot: false, category: 'ntd' },
|
||
{ value: '.car', label: '.car', hot: false, category: 'ntd' },
|
||
{ value: '.coffee', label: '.coffee', hot: false, category: 'ntd' },
|
||
{ value: '.college', label: '.college', hot: false, category: 'ntd' },
|
||
{ value: '.accountant', label: '.accountant', hot: false, category: 'ntd' },
|
||
{ value: '.auction', label: '.auction', hot: false, category: 'ntd' },
|
||
{ value: '.audio', label: '.audio', hot: false, category: 'ntd' },
|
||
{ value: '.autos', label: '.autos', hot: false, category: 'ntd' },
|
||
{ value: '.baby', label: '.baby', hot: false, category: 'ntd' },
|
||
{ value: '.band', label: '.band', hot: false, category: 'ntd' },
|
||
{ value: '.bar', label: '.bar', hot: false, category: 'ntd' },
|
||
{ value: '.beauty', label: '.beauty', hot: false, category: 'ntd' },
|
||
{ value: '.beer', label: '.beer', hot: false, category: 'ntd' },
|
||
{ value: '.best', label: '.best', hot: false, category: 'ntd' },
|
||
{ value: '.bet', label: '.bet', hot: false, category: 'ntd' },
|
||
{ value: '.bid', label: '.bid', hot: false, category: 'ntd' },
|
||
{ value: '.bike', label: '.bike', hot: false, category: 'ntd' },
|
||
{ value: '.bio', label: '.bio', hot: false, category: 'ntd' },
|
||
{ value: '.black', label: '.black', hot: false, category: 'ntd' },
|
||
{ value: '.blog', label: '.blog', hot: false, category: 'ntd' },
|
||
{ value: '.blue', label: '.blue', hot: false, category: 'ntd' },
|
||
{ value: '.boats', label: '.boats', hot: false, category: 'ntd' },
|
||
{ value: '.bond', label: '.bond', hot: false, category: 'ntd' },
|
||
{ value: '.business', label: '.business', hot: false, category: 'ntd' },
|
||
{ value: '.buzz', label: '.buzz', hot: false, category: 'ntd' },
|
||
{ value: '.bz', label: '.bz', hot: false, category: 'ntd' },
|
||
{ value: '.construction', label: '.construction', hot: false, category: 'ntd' },
|
||
{ value: '.cool', label: '.cool', hot: false, category: 'ntd' },
|
||
{ value: '.credit', label: '.credit', hot: false, category: 'ntd' },
|
||
{ value: '.cricket', label: '.cricket', hot: false, category: 'ntd' },
|
||
{ value: '.cx', label: '.cx', hot: false, category: 'ntd' },
|
||
{ value: '.cyou', label: '.cyou', hot: false, category: 'ntd' },
|
||
{ value: '.date', label: '.date', hot: false, category: 'ntd' },
|
||
{ value: '.design', label: '.design', hot: false, category: 'ntd' },
|
||
{ value: '.diet', label: '.diet', hot: false, category: 'ntd' },
|
||
{ value: '.dog', label: '.dog', hot: false, category: 'ntd' },
|
||
{ value: '.domains', label: '.domains', hot: false, category: 'ntd' },
|
||
{ value: '.download', label: '.download', hot: false, category: 'ntd' },
|
||
{ value: '.email', label: '.email', hot: false, category: 'ntd' },
|
||
{ value: '.engineer', label: '.engineer', hot: false, category: 'ntd' },
|
||
{ value: '.gs', label: '.gs', hot: false, category: 'ntd' },
|
||
{ value: '.equipment', label: '.equipment', hot: false, category: 'ntd' },
|
||
{ value: '.estate', label: '.estate', hot: false, category: 'ntd' },
|
||
{ value: '.expert', label: '.expert', hot: false, category: 'ntd' },
|
||
{ value: '.faith', label: '.faith', hot: false, category: 'ntd' },
|
||
{ value: '.family', label: '.family', hot: false, category: 'ntd' },
|
||
{ value: '.fan', label: '.fan', hot: false, category: 'ntd' },
|
||
{ value: '.fans', label: '.fans', hot: false, category: 'ntd' },
|
||
{ value: '.fashion', label: '.fashion', hot: false, category: 'ntd' },
|
||
{ value: '.feedback', label: '.feedback', hot: false, category: 'ntd' },
|
||
{ value: '.fish', label: '.fish', hot: false, category: 'ntd' },
|
||
{ value: '.fit', label: '.fit', hot: false, category: 'ntd' },
|
||
{ value: '.flowers', label: '.flowers', hot: false, category: 'ntd' },
|
||
{ value: '.fund', label: '.fund', hot: false, category: 'ntd' },
|
||
{ value: '.fyi', label: '.fyi', hot: false, category: 'ntd' },
|
||
{ value: '.game', label: '.game', hot: false, category: 'ntd' },
|
||
{ value: '.games', label: '.games', hot: false, category: 'ntd' },
|
||
{ value: '.gg', label: '.gg', hot: false, category: 'ntd' },
|
||
{ value: '.gift', label: '.gift', hot: false, category: 'ntd' },
|
||
{ value: '.gives', label: '.gives', hot: false, category: 'ntd' },
|
||
{ value: '.global', label: '.global', hot: false, category: 'ntd' },
|
||
{ value: '.gold', label: '.gold', hot: false, category: 'ntd' },
|
||
{ value: '.green', label: '.green', hot: false, category: 'ntd' },
|
||
{ value: '.guru', label: '.guru', hot: false, category: 'ntd' },
|
||
{ value: '.hair', label: '.hair', hot: false, category: 'ntd' },
|
||
{ value: '.haus', label: '.haus', hot: false, category: 'ntd' },
|
||
{ value: '.help', label: '.help', hot: false, category: 'ntd' },
|
||
{ value: '.holiday', label: '.holiday', hot: false, category: 'ntd' },
|
||
{ value: '.homes', label: '.homes', hot: false, category: 'ntd' },
|
||
{ value: '.host', label: '.host', hot: false, category: 'ntd' },
|
||
{ value: '.hosting', label: '.hosting', hot: false, category: 'ntd' },
|
||
{ value: '.house', label: '.house', hot: false, category: 'ntd' },
|
||
{ value: '.xxx', label: '.xxx', hot: false, category: 'ntd' },
|
||
{ value: '.yachts', label: '.yachts', hot: false, category: 'ntd' },
|
||
{ value: '.yoga', label: '.yoga', hot: false, category: 'ntd' },
|
||
{ value: '.zone', label: '.zone', hot: false, category: 'ntd' },
|
||
|
||
// 国家/地区域名 (country)
|
||
{ value: '.cn', label: '.cn', hot: true, category: 'country' },
|
||
{ value: '.hk', label: '.hk', hot: true, category: 'country' },
|
||
{ value: '.asia', label: '.asia', hot: false, category: 'country' },
|
||
{ value: '.cc', label: '.cc', hot: false, category: 'country' },
|
||
{ value: '.sc', label: '.sc', hot: false, category: 'country' },
|
||
{ value: '.tw', label: '.tw', hot: false, category: 'country' },
|
||
{ value: '.tv', label: '.tv', hot: false, category: 'country' },
|
||
|
||
// CN域名 (cn)
|
||
{ value: '.net.cn', label: '.net.cn', hot: false, category: 'cn' },
|
||
{ value: '.org.cn', label: '.org.cn', hot: false, category: 'cn' },
|
||
{ value: '.gov.cn', label: '.gov.cn', hot: false, category: 'cn' },
|
||
{ value: '.cn.com', label: '.cn.com', hot: false, category: 'cn' },
|
||
{ value: '.com.hk', label: '.com.hk', hot: false, category: 'cn' },
|
||
{ value: '.com.tw', label: '.com.tw', hot: false, category: 'cn' },
|
||
{ value: '.net.co', label: '.net.co', hot: false, category: 'cn' },
|
||
{ value: '.com.co', label: '.com.co', hot: false, category: 'cn' },
|
||
|
||
// 中文域名 (chinese)
|
||
{ value: '.中国', label: '.中国', hot: true, category: 'chinese' },
|
||
{ value: '.网络', label: '.网络', hot: false, category: 'chinese' },
|
||
{ value: '.网址', label: '.网址', hot: false, category: 'chinese' },
|
||
{ value: '.商标', label: '.商标', hot: false, category: 'chinese' },
|
||
{ value: '.世界', label: '.世界', hot: false, category: 'chinese' },
|
||
{ value: '.集团', label: '.集团', hot: false, category: 'chinese' },
|
||
{ value: '.我爱你', label: '.我爱你', hot: false, category: 'chinese' },
|
||
{ value: '.餐厅', label: '.餐厅', hot: false, category: 'chinese' },
|
||
{ value: '.购物', label: '.购物', hot: false, category: 'chinese' },
|
||
{ value: '.健康', label: '.健康', hot: false, category: 'chinese' },
|
||
{ value: '.企业', label: '.企业', hot: false, category: 'chinese' },
|
||
{ value: '.商店', label: '.商店', hot: false, category: 'chinese' },
|
||
{ value: '.网店', label: '.网店', hot: false, category: 'chinese' },
|
||
{ value: '.娱乐', label: '.娱乐', hot: false, category: 'chinese' },
|
||
{ value: '.游戏', label: '.游戏', hot: false, category: 'chinese' },
|
||
{ value: '.招聘', label: '.招聘', hot: false, category: 'chinese' },
|
||
{ value: '.佛山', label: '.佛山', hot: false, category: 'chinese' },
|
||
{ value: '.广东', label: '.广东', hot: false, category: 'chinese' },
|
||
{ value: '.公司', label: '.公司', hot: false, category: 'chinese' },
|
||
{ value: '.信息', label: '.信息', hot: false, category: 'chinese' },
|
||
{ value: '.中文网', label: '.中文网', hot: false, category: 'chinese' },
|
||
{ value: '.在线', label: '.在线', hot: false, category: 'chinese' },
|
||
{ value: '.网址', label: '.网址', hot: false, category: 'chinese' },
|
||
{ value: '.商标', label: '.商标', hot: false, category: 'chinese' },
|
||
{ value: '.世界', label: '.世界', hot: false, category: 'chinese' },
|
||
{ value: '.集团', label: '.集团', hot: false, category: 'chinese' },
|
||
{ value: '.我爱你', label: '.我爱你', hot: false, category: 'chinese' },
|
||
{ value: '.餐厅', label: '.餐厅', hot: false, category: 'chinese' },
|
||
{ value: '.购物', label: '.购物', hot: false, category: 'chinese' },
|
||
{ value: '.健康', label: '.健康', hot: false, category: 'chinese' },
|
||
{ value: '.企业', label: '.企业', hot: false, category: 'chinese' },
|
||
{ value: '.商店', label: '.商店', hot: false, category: 'chinese' },
|
||
{ value: '.网店', label: '.网店', hot: false, category: 'chinese' },
|
||
{ value: '.娱乐', label: '.娱乐', hot: false, category: 'chinese' },
|
||
{ value: '.游戏', label: '.游戏', hot: false, category: 'chinese' },
|
||
{ value: '.招聘', label: '.招聘', hot: false, category: 'chinese' },
|
||
{ value: '.佛山', label: '.佛山', hot: false, category: 'chinese' },
|
||
{ value: '.广东', label: '.广东', hot: false, category: 'chinese' }
|
||
]
|
||
};
|
||
},
|
||
computed: {
|
||
fullDomain() {
|
||
if (!this.domainName) return '';
|
||
return this.domainName + this.selectedSuffix;
|
||
},
|
||
filteredSuffixes() {
|
||
let suffixes = this.allSuffixes;
|
||
|
||
// 根据分类过滤
|
||
if (this.selectedCategory === 'popular') {
|
||
suffixes = suffixes.filter(s => s.hot);
|
||
} else if (this.selectedCategory === 'gtd') {
|
||
suffixes = suffixes.filter(s => s.category === 'gtd');
|
||
} else if (this.selectedCategory === 'ntd') {
|
||
suffixes = suffixes.filter(s => s.category === 'ntd');
|
||
} else if (this.selectedCategory === 'country') {
|
||
suffixes = suffixes.filter(s => s.category === 'country');
|
||
} else if (this.selectedCategory === 'cn') {
|
||
suffixes = suffixes.filter(s => s.category === 'cn');
|
||
} else if (this.selectedCategory === 'chinese') {
|
||
suffixes = suffixes.filter(s => s.category === 'chinese');
|
||
} else if (this.selectedCategory === 'business') {
|
||
suffixes = suffixes.filter(s => ['.com', '.biz', '.business', '.shop', '.store', '.market', '.trade', '.sale', '.commerce', '.company', '.enterprise', '.corp', '.inc', '.ltd'].includes(s.value));
|
||
} else if (this.selectedCategory === 'tech') {
|
||
suffixes = suffixes.filter(s => ['.tech', '.technology', '.ai', '.io', '.dev', '.app', '.software', '.digital', '.cloud', '.data', '.api', '.code', '.hack', '.lab', '.studio'].includes(s.value));
|
||
} else if (this.selectedCategory === 'creative') {
|
||
suffixes = suffixes.filter(s => ['.art', '.design', '.creative', '.studio', '.gallery', '.photo', '.photography', '.video', '.media', '.film', '.music', '.band', '.show', '.stage'].includes(s.value));
|
||
} else if (this.selectedCategory === 'lifestyle') {
|
||
suffixes = suffixes.filter(s => ['.life', '.lifestyle', '.health', '.fitness', '.beauty', '.fashion', '.style', '.luxury', '.premium', '.elite', '.vip', '.exclusive', '.boutique', '.spa', '.wellness'].includes(s.value));
|
||
}
|
||
|
||
// 根据搜索关键词过滤
|
||
if (this.suffixSearch) {
|
||
const searchLower = this.suffixSearch.toLowerCase();
|
||
suffixes = suffixes.filter(s => s.label.toLowerCase().includes(searchLower));
|
||
}
|
||
|
||
// 根据排序方式排序
|
||
if (this.suffixSort === 'popular') {
|
||
suffixes.sort((a, b) => (b.hot ? 1 : 0) - (a.hot ? 1 : 0));
|
||
} else if (this.suffixSort === 'alphabetical') {
|
||
suffixes.sort((a, b) => a.label.localeCompare(b.label));
|
||
}
|
||
|
||
return suffixes;
|
||
}
|
||
},
|
||
resources: {
|
||
// 查询域名可用性
|
||
checkDomainAvailability() {
|
||
return {
|
||
url: 'jcloud.api.domain_west.check_domain',
|
||
validate() {
|
||
if (!this.domainName) {
|
||
throw new DashboardError('请输入域名');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
if (response.status === "Error") {
|
||
this.error = response.message || '域名查询失败';
|
||
this.domainCheckResult = { available: false };
|
||
return;
|
||
}
|
||
|
||
this.domainCheckResult = response;
|
||
this.error = null;
|
||
|
||
// 如果域名可用,获取价格
|
||
if (response.available) {
|
||
this.getDomainPrice();
|
||
}
|
||
},
|
||
onError(error) {
|
||
this.error = error.message || '域名查询失败';
|
||
this.domainCheckResult = { available: false };
|
||
}
|
||
};
|
||
},
|
||
// 获取域名价格
|
||
getDomainPrice() {
|
||
return {
|
||
url: 'jcloud.api.domain_west.get_west_domain_price',
|
||
onSuccess(response) {
|
||
if (response.status === "Error") {
|
||
this.domainPrice = null;
|
||
return;
|
||
}
|
||
|
||
// 解析价格信息
|
||
if (response.data && response.data.price) {
|
||
this.domainPrice = response.data.price;
|
||
} else {
|
||
this.domainPrice = 50; // 默认价格
|
||
}
|
||
},
|
||
onError(error) {
|
||
this.domainPrice = 50; // 默认价格
|
||
}
|
||
};
|
||
},
|
||
// 创建域名注册订单
|
||
createDomainOrder() {
|
||
return {
|
||
url: 'jcloud.api.domain_west.create_domain_order',
|
||
validate() {
|
||
if (!this.domainCheckResult || !this.domainCheckResult.available) {
|
||
throw new DashboardError('请先查询域名可用性');
|
||
}
|
||
if (!this.selectedPaymentMethod) {
|
||
throw new DashboardError('请选择支付方式');
|
||
}
|
||
},
|
||
onSuccess(data) {
|
||
if (!data.success) {
|
||
this.error = data.message || '创建域名订单失败';
|
||
return;
|
||
}
|
||
|
||
// 显示订单支付界面
|
||
this.order = data.order;
|
||
this.domain = data.domain;
|
||
this.showPaymentProcessing = true;
|
||
|
||
// 立即处理支付
|
||
this.processPayment();
|
||
},
|
||
onError(error) {
|
||
this.error = error.message || '创建域名订单失败';
|
||
}
|
||
};
|
||
},
|
||
// 处理余额支付
|
||
processBalancePayment() {
|
||
return {
|
||
url: 'jcloud.api.billing.process_balance_payment_for_domain_order',
|
||
validate() {
|
||
if (!this.order || !this.order.order_id) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
if (response.status === "Error" || response.success === false) {
|
||
toast.error(response.message || '支付失败,请确保余额充足');
|
||
this.error = response.message || '余额不足';
|
||
this.isProcessingPayment = false;
|
||
return;
|
||
}
|
||
|
||
this.isProcessingPayment = false;
|
||
this.paymentSuccess = true;
|
||
|
||
// 支付成功后跳转到域名列表
|
||
setTimeout(() => {
|
||
this.$router.push('/domains');
|
||
}, 30000);
|
||
},
|
||
onError(error) {
|
||
this.error = error.message || '余额支付处理失败';
|
||
this.isProcessingPayment = false;
|
||
}
|
||
};
|
||
},
|
||
// 处理支付宝支付
|
||
processAlipayPayment() {
|
||
return {
|
||
url: 'jcloud.api.billing.process_alipay_order',
|
||
validate() {
|
||
if (!this.order || !this.order.order_id) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
this.paymentUrl = response.payment_url;
|
||
window.open(response.payment_url, '_blank');
|
||
|
||
toast.success('支付页面已在新窗口打开');
|
||
|
||
// 开始轮询支付状态
|
||
this.startPaymentCheck();
|
||
},
|
||
onError(error) {
|
||
this.error = error.message || '支付宝支付处理失败';
|
||
this.isProcessingPayment = false;
|
||
}
|
||
};
|
||
},
|
||
// 处理微信支付
|
||
processWechatPayment() {
|
||
return {
|
||
url: 'jcloud.api.billing.process_wechatpay_order',
|
||
validate() {
|
||
if (!this.order || !this.order.order_id) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(response) {
|
||
this.paymentQrCode = response.payment_url;
|
||
this.paymentQrCodeImage = response.qr_code_image || null;
|
||
|
||
// 开始轮询支付状态
|
||
this.startPaymentCheck();
|
||
},
|
||
onError(error) {
|
||
this.error = error.message || '微信支付处理失败';
|
||
this.isProcessingPayment = false;
|
||
}
|
||
};
|
||
},
|
||
// 检查支付状态
|
||
checkPaymentStatus() {
|
||
return {
|
||
url: 'jcloud.api.billing.check_order_payment_status',
|
||
params: {
|
||
order_id: this.order?.order_id
|
||
},
|
||
validate() {
|
||
if (!this.order || !this.order.order_id) {
|
||
throw new DashboardError('缺少订单信息');
|
||
}
|
||
},
|
||
onSuccess(data) {
|
||
if (data && data.status === '已支付') {
|
||
// 支付成功,停止轮询
|
||
this.stopPaymentCheck();
|
||
|
||
this.paymentSuccess = true;
|
||
|
||
// 支付成功后跳转到域名列表
|
||
setTimeout(() => {
|
||
this.$router.push('/domains');
|
||
}, 30000);
|
||
}
|
||
}
|
||
};
|
||
},
|
||
// 获取域名所有者列表
|
||
getDomainOwners() {
|
||
return {
|
||
url: 'jcloud.api.domain_west.get_domain_owners',
|
||
onSuccess(response) {
|
||
if (response.status === "Success") {
|
||
this.domainOwners = response.data || [];
|
||
}
|
||
},
|
||
onError(error) {
|
||
console.error('获取域名所有者列表失败:', error);
|
||
}
|
||
};
|
||
},
|
||
// 创建域名所有者
|
||
createDomainOwner() {
|
||
return {
|
||
url: 'jcloud.api.domain_west.create_domain_owner_with_template',
|
||
onSuccess(response) {
|
||
if (response.status === "Success") {
|
||
toast.success('域名所有者创建成功');
|
||
this.showNewOwnerDialog = false;
|
||
this.isCreatingOwner = false;
|
||
// 重新获取域名所有者列表
|
||
this.$resources.getDomainOwners.submit();
|
||
} else {
|
||
toast.error(response.message || '创建失败');
|
||
this.isCreatingOwner = false;
|
||
}
|
||
},
|
||
onError(error) {
|
||
toast.error(error.message || '创建域名所有者失败');
|
||
this.isCreatingOwner = false;
|
||
}
|
||
};
|
||
},
|
||
|
||
},
|
||
mounted() {
|
||
// 添加点击外部关闭域名后缀选择区块的功能
|
||
document.addEventListener('click', this.handleClickOutside);
|
||
|
||
// 加载域名所有者列表
|
||
this.$resources.getDomainOwners.submit();
|
||
},
|
||
beforeUnmount() {
|
||
this.stopPaymentCheck();
|
||
document.removeEventListener('click', this.handleClickOutside);
|
||
},
|
||
methods: {
|
||
async checkDomain() {
|
||
if (!this.domainName) {
|
||
this.error = '请输入域名';
|
||
return;
|
||
}
|
||
|
||
this.error = null;
|
||
this.isChecking = true;
|
||
|
||
await this.$resources.checkDomainAvailability.submit({
|
||
domain: this.domainName,
|
||
suffix: this.selectedSuffix
|
||
});
|
||
|
||
this.isChecking = false;
|
||
},
|
||
async getDomainPrice() {
|
||
await this.$resources.getDomainPrice.submit({
|
||
domain: this.fullDomain,
|
||
year: 1
|
||
});
|
||
},
|
||
async registerDomain() {
|
||
if (!this.domainCheckResult || !this.domainCheckResult.available) {
|
||
this.error = '请先查询域名可用性';
|
||
return;
|
||
}
|
||
if (!this.selectedDomainOwner) {
|
||
this.error = '请选择域名所有者';
|
||
return;
|
||
}
|
||
if (!this.selectedPaymentMethod) {
|
||
this.error = '请选择支付方式';
|
||
return;
|
||
}
|
||
|
||
this.error = null;
|
||
this.isLoading = true;
|
||
|
||
await this.$resources.createDomainOrder.submit({
|
||
domain: this.fullDomain,
|
||
period: this.period,
|
||
payment_method: this.selectedPaymentMethod,
|
||
domain_owner: this.selectedDomainOwner
|
||
});
|
||
|
||
this.isLoading = false;
|
||
},
|
||
startPaymentCheck() {
|
||
this.isProcessingPayment = false;
|
||
|
||
this.checkInterval = setInterval(() => {
|
||
this.$resources.checkPaymentStatus.submit();
|
||
}, 3000);
|
||
|
||
// 15分钟后停止检查
|
||
setTimeout(() => {
|
||
this.stopPaymentCheck();
|
||
}, 900000);
|
||
},
|
||
stopPaymentCheck() {
|
||
if (this.checkInterval) {
|
||
clearInterval(this.checkInterval);
|
||
this.checkInterval = null;
|
||
}
|
||
},
|
||
processPayment() {
|
||
this.isProcessingPayment = true;
|
||
this.error = null;
|
||
|
||
if (!this.order || !this.order.order_id) {
|
||
this.error = '订单信息不完整,请重试';
|
||
this.isProcessingPayment = false;
|
||
return;
|
||
}
|
||
|
||
if (this.selectedPaymentMethod === 'balance') {
|
||
this.$resources.processBalancePayment.submit({
|
||
order_id: this.order.order_id
|
||
});
|
||
} else if (this.selectedPaymentMethod === 'alipay') {
|
||
this.$resources.processAlipayPayment.submit({
|
||
order_id: this.order.order_id
|
||
});
|
||
} else if (this.selectedPaymentMethod === 'wechatpay') {
|
||
this.$resources.processWechatPayment.submit({
|
||
order_id: this.order.order_id
|
||
});
|
||
}
|
||
},
|
||
goToDomainList() {
|
||
this.$router.push('/domains');
|
||
},
|
||
handleOwnerSubmit(formData) {
|
||
this.isCreatingOwner = true;
|
||
|
||
// 直接提交数据,验证由对话框组件负责
|
||
this.$resources.createDomainOwner.submit(formData);
|
||
},
|
||
getTotalAmount() {
|
||
const yearlyPrice = this.domainPrice || 0;
|
||
return (yearlyPrice * this.period).toFixed(2);
|
||
},
|
||
selectSuffix(suffix) {
|
||
this.selectedSuffix = suffix;
|
||
this.showSuffixSelector = false;
|
||
},
|
||
handleClickOutside(event) {
|
||
const suffixButton = event.target.closest('button');
|
||
const suffixSelector = event.target.closest('.border.rounded-lg.p-4.bg-gray-50');
|
||
|
||
if (!suffixButton || !suffixButton.classList.contains('suffix-selector-btn')) {
|
||
if (!suffixSelector) {
|
||
this.showSuffixSelector = false;
|
||
}
|
||
}
|
||
},
|
||
getOwnerDisplayName(owner) {
|
||
// 如果是个人,优先显示fullname,没有的话显示title
|
||
if (owner.c_regtype === 'I') {
|
||
return owner.fullname || owner.title;
|
||
}
|
||
// 如果是公司,优先显示c_org_m,没有的话显示title
|
||
else if (owner.c_regtype === 'E') {
|
||
return owner.c_org_m || owner.title;
|
||
}
|
||
// 默认显示title
|
||
return owner.title;
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.qrcode-container {
|
||
height: 250px;
|
||
width: 250px;
|
||
margin: 0 auto;
|
||
padding: 5px;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 12px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.qrcode-image {
|
||
height: 100%;
|
||
width: 100%;
|
||
object-fit: contain;
|
||
}
|
||
|
||
/* 域名后缀网格样式 */
|
||
.domain-suffix-grid {
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
.domain-suffix-item {
|
||
transition: all 0.2s ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.domain-suffix-item:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.domain-suffix-item.selected {
|
||
border-color: #3b82f6;
|
||
background: #eff6ff;
|
||
color: #1d4ed8;
|
||
}
|
||
|
||
.domain-suffix-item.selected .hot-tag {
|
||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||
color: white;
|
||
}
|
||
|
||
/* 分类标签样式 */
|
||
.category-tabs {
|
||
margin-bottom: 16px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
|
||
.category-tab {
|
||
padding: 6px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
transition: all 0.2s ease;
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.category-tab.active {
|
||
background: #eff6ff;
|
||
color: #1d4ed8;
|
||
border-color: #3b82f6;
|
||
border-width: 2px;
|
||
}
|
||
|
||
.category-tab:not(.active) {
|
||
background: white;
|
||
color: #6b7280;
|
||
border-color: #e5e7eb;
|
||
}
|
||
|
||
.category-tab:not(.active):hover {
|
||
background: #f9fafb;
|
||
border-color: #d1d5db;
|
||
}
|
||
|
||
/* 搜索框样式 */
|
||
.search-container {
|
||
position: relative;
|
||
flex: 1;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding-left: 36px;
|
||
padding-right: 12px;
|
||
padding-top: 8px;
|
||
padding-bottom: 8px;
|
||
font-size: 14px;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
background: white;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: #3b82f6;
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||
}
|
||
|
||
.search-icon {
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: #9ca3af;
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
/* 热门标签样式 */
|
||
.hot-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 2px 6px;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
|
||
color: white;
|
||
border-radius: 10px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
|
||
/* 添加渐入动画 */
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(10px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 640px) {
|
||
.domain-suffix-grid {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.category-tabs {
|
||
gap: 6px;
|
||
}
|
||
|
||
.category-tab {
|
||
padding: 4px 8px;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.domain-suffix-item {
|
||
padding: 8px 4px;
|
||
}
|
||
|
||
.domain-suffix-item .text-sm {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.hot-tag {
|
||
font-size: 9px;
|
||
padding: 1px 4px;
|
||
}
|
||
|
||
.search-input {
|
||
font-size: 14px;
|
||
padding: 8px 12px 8px 32px;
|
||
}
|
||
|
||
.search-icon {
|
||
width: 14px;
|
||
height: 14px;
|
||
left: 10px;
|
||
}
|
||
}
|
||
|
||
@media (min-width: 641px) and (max-width: 768px) {
|
||
.domain-suffix-grid {
|
||
grid-template-columns: repeat(3, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 769px) and (max-width: 1024px) {
|
||
.domain-suffix-grid {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1025px) and (max-width: 1280px) {
|
||
.domain-suffix-grid {
|
||
grid-template-columns: repeat(6, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1281px) and (max-width: 1536px) {
|
||
.domain-suffix-grid {
|
||
grid-template-columns: repeat(8, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (min-width: 1537px) {
|
||
.domain-suffix-grid {
|
||
grid-template-columns: repeat(10, 1fr);
|
||
}
|
||
}
|
||
</style> |