952 lines
32 KiB
Vue
952 lines
32 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"
|
||
/>
|
||
<button
|
||
@click="checkDomain"
|
||
:disabled="!domainName || isChecking"
|
||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-300 whitespace-nowrap"
|
||
>
|
||
{{ isChecking ? '查询中...' : '查询域名' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 域名后缀选择区域 -->
|
||
<div 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="selectedSuffix = 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>
|
||
<div v-if="suffix.price" class="text-xs mt-1 text-gray-500">
|
||
¥{{ suffix.price }}/年
|
||
</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">
|
||
<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-3 分钟,请耐心等待。
|
||
</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>
|
||
|
||
<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>
|
||
</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 router from '../router';
|
||
|
||
export default {
|
||
name: 'NewJsiteDomain',
|
||
components: {
|
||
AlipayLogo,
|
||
WeChatPayLogo
|
||
},
|
||
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: 'all',
|
||
suffixCategories: [
|
||
{ key: 'all', label: '全部域名' },
|
||
{ key: 'popular', label: '热门域名' },
|
||
{ key: 'gtd', label: '通用顶级域名' },
|
||
{ key: 'ntd', label: '新顶级域名' },
|
||
{ key: 'country', label: '国家/地区域名' },
|
||
{ key: 'cn', label: 'CN域名' },
|
||
{ key: 'chinese', label: '中文域名' }
|
||
],
|
||
allSuffixes: [
|
||
{ value: '.com', label: '.com', hot: true, price: 50, category: 'com' },
|
||
{ value: '.cn', label: '.cn', hot: true, price: 50, category: 'cn' },
|
||
{ value: '.net', label: '.net', hot: true, price: 50, category: 'net' },
|
||
{ value: '.org', label: '.org', hot: true, price: 50, category: 'org' },
|
||
{ value: '.top', label: '.top', hot: true, price: 50, category: 'top' },
|
||
{ value: '.xyz', label: '.xyz', hot: true, price: 50, category: 'xyz' },
|
||
{ value: '.vip', label: '.vip', hot: true, price: 50, category: 'vip' },
|
||
{ value: '.site', label: '.site', hot: true, price: 50, category: 'site' },
|
||
{ value: '.shop', label: '.shop', hot: true, price: 50, category: 'shop' },
|
||
{ value: '.io', label: '.io', hot: true, price: 50, category: 'io' },
|
||
{ value: '.ai', label: '.ai', hot: true, price: 50, category: 'ai' },
|
||
{ value: '.me', label: '.me', hot: true, price: 50, category: 'me' },
|
||
{ value: '.co', label: '.co', hot: true, price: 50, category: 'co' },
|
||
{ value: '.dev', label: '.dev', hot: true, price: 50, category: 'dev' },
|
||
{ value: '.app', label: '.app', hot: true, price: 50, category: 'app' },
|
||
{ value: '.fun', label: '.fun', hot: false, price: 45, category: 'fun' },
|
||
{ value: '.tech', label: '.tech', hot: false, price: 45, category: 'tech' },
|
||
{ value: '.art', label: '.art', hot: true, price: 45, category: 'art' },
|
||
{ value: '.group', label: '.group', hot: false, price: 45, category: 'group' },
|
||
{ value: '.net.cn', label: '.net.cn', hot: false, price: 45, category: 'cn' },
|
||
{ value: '.work', label: '.work', hot: false, price: 45, category: 'work' },
|
||
{ value: '.asia', label: '.asia', hot: false, price: 45, category: 'asia' },
|
||
{ value: '.hk', label: '.hk', hot: true, price: 45, category: 'hk' },
|
||
{ value: '.cc', label: '.cc', hot: false, price: 45, category: 'cc' },
|
||
{ value: '.icu', label: '.icu', hot: true, price: 45, category: 'icu' },
|
||
{ value: '.online', label: '.online', hot: false, price: 45, category: 'online' },
|
||
{ value: '.xin', label: '.xin', hot: false, price: 45, category: 'xin' },
|
||
{ value: '.club', label: '.club', hot: true, price: 45, category: 'club' },
|
||
{ value: '.info', label: '.info', hot: false, price: 45, category: 'info' },
|
||
{ value: '.ink', label: '.ink', hot: false, price: 45, category: 'ink' },
|
||
{ value: '.love', label: '.love', hot: false, price: 45, category: 'love' },
|
||
{ value: '.store', label: '.store', hot: false, price: 45, category: 'store' },
|
||
{ value: '.中国', label: '.中国', hot: true, price: 45, category: 'chinese' },
|
||
{ value: '.网络', label: '.网络', hot: false, price: 45, category: 'chinese' },
|
||
{ value: '.公司', label: '.公司', hot: false, price: 45, category: 'chinese' },
|
||
{ value: '.org.cn', label: '.org.cn', hot: false, price: 45, category: 'cn' },
|
||
{ value: '.gov.cn', label: '.gov.cn', hot: false, price: 45, category: 'cn' }
|
||
]
|
||
};
|
||
},
|
||
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 => ['.com', '.net', '.org', '.info'].includes(s.value));
|
||
} else if (this.selectedCategory === 'ntd') {
|
||
suffixes = suffixes.filter(s => ['.xyz', '.top', '.vip', '.site', '.shop', '.club', '.icu', '.online', '.love', '.store'].includes(s.value));
|
||
} else if (this.selectedCategory === 'country') {
|
||
suffixes = suffixes.filter(s => ['.cn', '.hk', '.asia', '.cc'].includes(s.value));
|
||
} else if (this.selectedCategory === 'cn') {
|
||
suffixes = suffixes.filter(s => s.value.includes('.cn'));
|
||
} else if (this.selectedCategory === 'chinese') {
|
||
suffixes = suffixes.filter(s => s.category === 'chinese');
|
||
}
|
||
|
||
// 根据搜索关键词过滤
|
||
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);
|
||
}
|
||
}
|
||
};
|
||
},
|
||
},
|
||
beforeUnmount() {
|
||
this.stopPaymentCheck();
|
||
},
|
||
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.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
|
||
});
|
||
|
||
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');
|
||
},
|
||
getTotalAmount() {
|
||
const yearlyPrice = this.domainPrice || 0;
|
||
return (yearlyPrice * this.period).toFixed(2);
|
||
}
|
||
},
|
||
};
|
||
</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> |