jcloud/dashboard/src2/pages/NewJsiteDomain.vue

1340 lines
55 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>
<!-- 页面头部 -->
<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>