mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-13 18:10:53 +08:00
【初始化】前端工程项目
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { NButton, NImage, NBadge } from 'naive-ui'
|
||||
import { $t } from '@locales/index'
|
||||
interface FreeProductCardProps {
|
||||
product: {
|
||||
pid: number
|
||||
brand: string
|
||||
type: string
|
||||
title: string
|
||||
code: string
|
||||
num: number
|
||||
valid_days: number
|
||||
features: string[]
|
||||
}
|
||||
onApply: (id: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 免费SSL证书产品卡片组件
|
||||
* @param product - 产品信息
|
||||
* @param onApply - 申请按钮点击处理函数
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'FreeProductCard',
|
||||
props: {
|
||||
product: {
|
||||
type: Object as PropType<FreeProductCardProps['product']>,
|
||||
required: true,
|
||||
},
|
||||
onApply: {
|
||||
type: Function as PropType<FreeProductCardProps['onApply']>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
// 判断是否为通配符证书
|
||||
const isWildcard = computed(() => {
|
||||
return props.product.title.toLowerCase().includes($t('t_10_1746667589575'))
|
||||
})
|
||||
|
||||
// 判断是否为多域名证书
|
||||
const isMultiDomain = computed(() => {
|
||||
return props.product.title.toLowerCase().includes($t('t_11_1746667589598'))
|
||||
})
|
||||
|
||||
// 处理申请按钮点击
|
||||
const handleApply = () => {
|
||||
props.onApply(props.product.pid)
|
||||
}
|
||||
|
||||
// 获取品牌图标
|
||||
const getBrandIcon = (brand: string) => {
|
||||
const brandLower = brand.toLowerCase()
|
||||
const brandIconMap: Record<string, string> = {
|
||||
sectigo: '/static/icons/sectigo-ico.png',
|
||||
positive: '/static/icons/positive-ico.png',
|
||||
ssltrus: '/static/icons/ssltrus-ico.png',
|
||||
"let's encrypt": '/static/icons/letsencrypt-icon.svg',
|
||||
}
|
||||
return Object.keys(brandIconMap).find((key) => brandLower.includes(key))
|
||||
? brandIconMap[Object.keys(brandIconMap).find((key) => brandLower.includes(key)) as string]
|
||||
: undefined
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class="relative border border-gray-200 rounded-[0.8rem] p-[2rem] transition-all duration-300 h-full flex flex-col bg-white shadow-sm hover:shadow-md hover:border-blue-100 hover:-translate-y-[0.2rem]">
|
||||
{props.product.brand === "Let's Encrypt" && (
|
||||
<div class="absolute top-[1.2rem] right-[1.2rem] z-10">
|
||||
<NBadge type="info" value={$t('t_12_1746667589733')} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex flex-col items-center text-center mb-[2rem] pb-[1.6rem] border-b border-gray-100">
|
||||
<div class="flex-none h-[6rem] w-2/5 mb-[1.2rem] flex items-center justify-center">
|
||||
<NImage
|
||||
src={getBrandIcon(props.product.brand)}
|
||||
fallbackSrc="/static/icons/default.png"
|
||||
alt={props.product.brand}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 w-full">
|
||||
<h3 class="font-semibold mb-[0.8rem] text-gray-800 leading-tight">{props.product.title}</h3>
|
||||
<p class="text-[1.3rem] text-gray-500 m-0 leading-relaxed px-[0.8rem]">
|
||||
{props.product.brand + $t('t_13_1746667599218')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col mt-0">
|
||||
<div class="text-[1.3rem] mb-[2.4rem] flex-1 text-left">
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">{$t('t_14_1746667590827')}</span>
|
||||
<span class="flex-1 text-gray-700">{props.product.num + $t('t_15_1746667588493')}</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">{$t('t_16_1746667591069')}</span>
|
||||
<span class="flex-1 text-gray-700">
|
||||
{isWildcard.value ? $t('t_17_1746667588785') : $t('t_18_1746667590113')}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">{$t('t_19_1746667589295')}</span>
|
||||
<span class="flex-1 text-gray-700">{props.product.valid_days + $t('t_20_1746667588453')}</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">{$t('t_21_1746667590834')}</span>
|
||||
<span class="flex-1 text-gray-700">{$t('t_17_1746667588785')}</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed whitespace-nowrap overflow-hidden text-ellipsis text-gray-500">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">{$t('t_22_1746667591024')}</span>
|
||||
<span class="flex-1 text-gray-600 whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{isWildcard.value
|
||||
? isMultiDomain.value
|
||||
? $t('t_23_1746667591989')
|
||||
: $t('t_24_1746667583520')
|
||||
: isMultiDomain.value
|
||||
? $t('t_25_1746667590147')
|
||||
: $t('t_26_1746667594662')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-[1.6rem] pt-[1.6rem] border-t border-gray-100">
|
||||
<div class="flex-1 flex flex-col">
|
||||
<div class="flex items-baseline justify-start">
|
||||
<span class="text-[2.2rem] font-bold text-green-500 leading-tight">{$t('t_27_1746667589350')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<NButton
|
||||
type="primary"
|
||||
class="flex-none transition-all duration-300 min-w-[9rem] hover:scale-105 hover:shadow-md"
|
||||
onClick={handleApply}
|
||||
strong
|
||||
round
|
||||
>
|
||||
{$t('t_28_1746667590336')}
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import { useCertificateFormController } from '@certApply/useController'
|
||||
|
||||
/**
|
||||
* 证书申请表单组件
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'CertificateForm',
|
||||
setup() {
|
||||
const { component: CertificateForm } = useCertificateFormController()
|
||||
return () => <CertificateForm labelPlacement="top" class="max-w-[50rem] mx-auto" />
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,173 @@
|
||||
import { NButton, NCard, NTag, NImage, NBadge, NList, NListItem, NTooltip } from 'naive-ui'
|
||||
|
||||
interface ProductCardProps {
|
||||
product: {
|
||||
pid: number
|
||||
brand: string
|
||||
type: string
|
||||
title: string
|
||||
add_price: number
|
||||
other_price: number
|
||||
num: number
|
||||
price: number
|
||||
discount: number
|
||||
state: number
|
||||
install_price: number
|
||||
src_price: number
|
||||
code: string
|
||||
}
|
||||
formatPrice: (price: number) => string
|
||||
onBuy: (id: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL证书产品卡片组件
|
||||
* @param product - 产品信息
|
||||
* @param formatPrice - 价格格式化函数
|
||||
* @param onBuy - 购买按钮点击处理函数
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'ProductCard',
|
||||
props: {
|
||||
product: {
|
||||
type: Object as PropType<ProductCardProps['product']>,
|
||||
required: true,
|
||||
},
|
||||
formatPrice: {
|
||||
type: Function as PropType<ProductCardProps['formatPrice']>,
|
||||
required: true,
|
||||
},
|
||||
onBuy: {
|
||||
type: Function as PropType<ProductCardProps['onBuy']>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
// 获取域名类型文本
|
||||
const domainTypeText = computed(() => {
|
||||
const title = props.product.title.toLowerCase()
|
||||
if (title.includes('通配符') && title.includes('多域名')) {
|
||||
return '多域名通配符'
|
||||
} else if (title.includes('通配符')) {
|
||||
return '通配符'
|
||||
} else if (title.includes('多域名')) {
|
||||
return '多域名'
|
||||
} else {
|
||||
return '单域名'
|
||||
}
|
||||
})
|
||||
|
||||
// 判断是否为通配符证书
|
||||
const isWildcard = computed(() => {
|
||||
return props.product.title.toLowerCase().includes('通配符')
|
||||
})
|
||||
|
||||
// 判断是否为多域名证书
|
||||
const isMultiDomain = computed(() => {
|
||||
return props.product.title.toLowerCase().includes('多域名')
|
||||
})
|
||||
|
||||
// 处理购买按钮点击
|
||||
const handleBuy = () => {
|
||||
props.onBuy(props.product.pid)
|
||||
}
|
||||
|
||||
// 获取品牌图标
|
||||
const getBrandIcon = (brand: string) => {
|
||||
const brandLower = brand.toLowerCase()
|
||||
if (brandLower.includes('sectigo')) return '/static/icons/sectigo-ico.png'
|
||||
if (brandLower.includes('positive')) return '/static/icons/positive-ico.png'
|
||||
if (brandLower.includes('锐安信')) return '/static/icons/ssltrus-ico.png'
|
||||
if (brandLower.includes("let's encrypt")) return '/static/icons/letsencrypt-icon.svg'
|
||||
// return '/static/icons/default.png'
|
||||
}
|
||||
|
||||
// // 显示的功能特点
|
||||
// const features = computed(() => {
|
||||
// return ['快速颁发', '支持全部浏览器', isWildcard.value ? '支持所有子域名' : '高安全性', '7*24小时技术支持']
|
||||
// })
|
||||
|
||||
return () => (
|
||||
<div class="relative border border-gray-200 rounded-[0.8rem] p-[2rem] transition-all duration-300 h-full flex flex-col bg-white shadow-sm hover:shadow-md hover:border-blue-100 hover:-translate-y-[0.2rem]">
|
||||
{props.product.discount < 1 && (
|
||||
<div class="absolute top-[1.2rem] right-[1.2rem] z-10">
|
||||
<NBadge type="success" value="推荐" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex flex-col items-center text-center mb-[2rem] pb-[1.6rem] border-b border-gray-100">
|
||||
<div class="flex-none h-[6rem] w-2/5 mb-[1.2rem] flex items-center justify-center">
|
||||
<NImage
|
||||
width="100%"
|
||||
src={getBrandIcon(props.product.brand)}
|
||||
fallbackSrc="/static/icons/default.png"
|
||||
alt={props.product.brand}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 w-full">
|
||||
<h3 class="font-semibold mb-[0.8rem] text-gray-800 leading-tight">{props.product.title}</h3>
|
||||
<p class="text-[1.3rem] text-gray-500 m-0 leading-relaxed px-[0.8rem]">
|
||||
{props.product.brand}是知名的证书颁发机构,提供高质量的SSL证书解决方案。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex flex-col mt-0">
|
||||
<div class="text-[1.3rem] mb-[2.4rem] flex-1 text-left">
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">支持域名数:</span>
|
||||
<span class="flex-1 text-gray-700">{props.product.num}个</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">支持通配符:</span>
|
||||
<span class="flex-1 text-gray-700">{isWildcard.value ? '支持' : '不支持'}</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">绿色地址栏:</span>
|
||||
<span class="flex-1 text-gray-700">{props.product.type.includes('EV') ? '显示' : '不显示'}</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">支持小程序:</span>
|
||||
<span class="flex-1 text-gray-700">支持</span>
|
||||
</div>
|
||||
<div class="flex mb-[1rem] leading-relaxed whitespace-nowrap overflow-hidden text-ellipsis text-gray-500">
|
||||
<span class="font-medium text-gray-500 flex-none w-[9rem]">适用网站:</span>
|
||||
<span class="flex-1 text-gray-600 whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{isWildcard.value
|
||||
? isMultiDomain.value
|
||||
? '*.bt.cn、*.btnode.cn'
|
||||
: '*.bt.cn'
|
||||
: isMultiDomain.value
|
||||
? 'bt.cn、btnode.cn'
|
||||
: 'www.bt.cn、bt.cn'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center mt-[1.6rem] pt-[1.6rem] border-t border-gray-100">
|
||||
<div class="flex-1 flex flex-col">
|
||||
<div class="flex items-baseline justify-start">
|
||||
<span class="text-[2.2rem] font-bold text-red-500 leading-tight">
|
||||
{props.formatPrice(props.product.price)}
|
||||
</span>
|
||||
<span class="text-[1.3rem] text-gray-400 ml-[0.4rem]">元/年</span>
|
||||
</div>
|
||||
<div class="text-[1.3rem] text-gray-400 line-through mt-[0.4rem]">
|
||||
原价 {props.formatPrice(props.product.other_price)}元/年
|
||||
</div>
|
||||
</div>
|
||||
<NButton
|
||||
type="primary"
|
||||
class="flex-none transition-all duration-300 min-w-[9rem] hover:scale-105 hover:shadow-md"
|
||||
onClick={handleBuy}
|
||||
strong
|
||||
round
|
||||
>
|
||||
立即查看
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
import { NCard, NText, NList, NListItem, NIcon, NDivider, NButton, NSpace } from 'naive-ui'
|
||||
import { SafetyCertificateOutlined, CheckCircleOutlined, InfoCircleOutlined } from '@vicons/antd'
|
||||
|
||||
interface SSLTypeInfoProps {
|
||||
certType: string
|
||||
typeInfo: {
|
||||
title: string
|
||||
features: string[]
|
||||
advantages: string
|
||||
disadvantages: string
|
||||
recommendation: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SSL证书类型说明组件
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'SSLTypeInfo',
|
||||
props: {
|
||||
certType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
typeInfo: {
|
||||
type: Object as PropType<SSLTypeInfoProps['typeInfo']>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
// 获取图标类型
|
||||
const getIcon = (certType: string) => {
|
||||
switch (certType) {
|
||||
case 'dv':
|
||||
return <SafetyCertificateOutlined />
|
||||
case 'ov':
|
||||
return <SafetyCertificateOutlined />
|
||||
case 'ev':
|
||||
return <SafetyCertificateOutlined />
|
||||
default:
|
||||
return <InfoCircleOutlined />
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图标颜色
|
||||
const getColor = (certType: string) => {
|
||||
switch (certType) {
|
||||
case 'dv':
|
||||
return '#18a058'
|
||||
case 'ov':
|
||||
return '#2080f0'
|
||||
case 'ev':
|
||||
return '#8a2be2'
|
||||
default:
|
||||
return '#999'
|
||||
}
|
||||
}
|
||||
|
||||
return () => (
|
||||
<NCard class="mb-[2.4rem]">
|
||||
<div class="flex items-center gap-[1.2rem]">
|
||||
<NIcon size={36} color={getColor(props.certType)}>
|
||||
{getIcon(props.certType)}
|
||||
</NIcon>
|
||||
<h3 class="m-0 text-lg">{props.typeInfo.title}</h3>
|
||||
</div>
|
||||
|
||||
<NDivider />
|
||||
|
||||
<div class="mb-[2rem]">
|
||||
<NText depth={2}>证书特点</NText>
|
||||
<NList>
|
||||
{props.typeInfo.features.map((feature, index) => (
|
||||
<NListItem key={index}>
|
||||
<NSpace align="center">
|
||||
<NIcon color={getColor(props.certType)} size={16}>
|
||||
<CheckCircleOutlined />
|
||||
</NIcon>
|
||||
<span>{feature}</span>
|
||||
</NSpace>
|
||||
</NListItem>
|
||||
))}
|
||||
</NList>
|
||||
|
||||
<div class="mb-[1.2rem]">
|
||||
<NText depth={2}>{props.typeInfo.advantages}</NText>
|
||||
</div>
|
||||
|
||||
<div class="mb-[1.2rem]">
|
||||
<NText depth={2}>{props.typeInfo.disadvantages}</NText>
|
||||
</div>
|
||||
|
||||
<div class="mt-[1.6rem] text-center">
|
||||
<NText strong>{props.typeInfo.recommendation}</NText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<NButton type="primary" onClick={() => window.open('https://www.bt.cn/new/ssl.html', '_blank')} block>
|
||||
了解更多详情
|
||||
</NButton>
|
||||
</div>
|
||||
</NCard>
|
||||
)
|
||||
},
|
||||
})
|
||||
98
frontend/apps/allin-ssl/src/views/certApply/index.tsx
Normal file
98
frontend/apps/allin-ssl/src/views/certApply/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { useController } from './useController'
|
||||
import { NTabs, NTabPane, NEmpty, NIcon } from 'naive-ui'
|
||||
import ProductCard from './components/productCard'
|
||||
import FreeProductCard from './components/freeProductCard'
|
||||
import { ShoppingCartOutlined, LockOutlined } from '@vicons/antd'
|
||||
|
||||
interface SSLTypeItem {
|
||||
type: 'dv' | 'ov' | 'ev'
|
||||
title: string
|
||||
explain: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const {
|
||||
activeMainTab,
|
||||
activeTab,
|
||||
mainTabOptions,
|
||||
sslTypeList,
|
||||
freeProducts,
|
||||
filteredProducts,
|
||||
handleBuyProduct,
|
||||
formatPrice,
|
||||
handleOpenApplyModal,
|
||||
} = useController()
|
||||
|
||||
return () => (
|
||||
<div class="w-full max-w-[160rem] mx-auto p-[2rem]">
|
||||
<div class="bg-white rounded-[0.8rem] shadow-lg p-[2.4rem] mb-[3rem]">
|
||||
{/* 主标签页:商业证书/免费证书 */}
|
||||
<NTabs
|
||||
class="rounded-[1.2rem] p-[0.6rem]"
|
||||
type="segment"
|
||||
v-model:value={activeMainTab.value}
|
||||
size="large"
|
||||
justifyContent="space-evenly"
|
||||
>
|
||||
{mainTabOptions.value.map((tab) => (
|
||||
<NTabPane key={tab.key} name={tab.key}>
|
||||
{{
|
||||
tab: () => (
|
||||
<div class="flex items-center my-[1rem] px-[0.8rem] py-[0.4rem] rounded-[0.8rem] transition-all duration-300 hover:bg-black/5 ">
|
||||
<NIcon size="20">{tab.key === 'commercial' ? <ShoppingCartOutlined /> : <LockOutlined />}</NIcon>
|
||||
<span class="ml-[0.8rem]">{tab.title}</span>
|
||||
</div>
|
||||
),
|
||||
default: () => (
|
||||
<div class="py-[0.4rem] rounded-[1.6rem]">
|
||||
{/* 商业证书内容 */}
|
||||
{activeMainTab.value === 'commercial' && (
|
||||
<NTabs
|
||||
class="w-full p-0 mt-[1.6rem] rounded-[0.8rem] overflow-hidden"
|
||||
type="line"
|
||||
v-model:value={activeTab.value}
|
||||
size="medium"
|
||||
justifyContent="space-evenly"
|
||||
>
|
||||
{(sslTypeList.value as SSLTypeItem[]).map((item: SSLTypeItem) => (
|
||||
<NTabPane key={item.type} name={item.type} tab={item.title}>
|
||||
<div class="flex flex-col gap-[2.4rem] mt-[1rem]">
|
||||
{/* 证书产品列表 */}
|
||||
{filteredProducts.value.length > 0 ? (
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{filteredProducts.value.map((product) => (
|
||||
<ProductCard
|
||||
key={product.pid}
|
||||
product={product}
|
||||
formatPrice={formatPrice}
|
||||
onBuy={handleBuyProduct}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<NEmpty description="暂无产品" />
|
||||
)}
|
||||
</div>
|
||||
</NTabPane>
|
||||
))}
|
||||
</NTabs>
|
||||
)}
|
||||
{activeMainTab.value === 'free' && (
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
{freeProducts.value.map((product) => (
|
||||
<FreeProductCard key={product.pid} product={product} onApply={handleOpenApplyModal} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
</NTabPane>
|
||||
))}
|
||||
</NTabs>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
200
frontend/apps/allin-ssl/src/views/certApply/useController.tsx
Normal file
200
frontend/apps/allin-ssl/src/views/certApply/useController.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import { FormRules } from 'naive-ui'
|
||||
import { useModal, useForm, useFormHooks, useLoadingMask, useModalHooks } from '@baota/naive-ui/hooks'
|
||||
import { useError } from '@baota/hooks/error'
|
||||
import { isDomain } from '@baota/utils/business'
|
||||
import { useStore as useWorkflowViewStore } from '@autoDeploy/children/workflowView/useStore'
|
||||
import { $t } from '@locales/index'
|
||||
import { useStore } from './useStore'
|
||||
import CertificateForm from './components/freeProductModal'
|
||||
import DnsProviderSelect from '@components/dnsProviderSelect'
|
||||
|
||||
// 错误处理
|
||||
const { handleError } = useError()
|
||||
|
||||
/**
|
||||
* useController
|
||||
* @description 组合式API使用store
|
||||
* @returns {object} store - 返回store对象
|
||||
*/
|
||||
export const useController = () => {
|
||||
const {
|
||||
test,
|
||||
handleTest,
|
||||
activeMainTab,
|
||||
activeTab,
|
||||
mainTabOptions,
|
||||
typeOptions,
|
||||
sslTypeList,
|
||||
sslTypeDescriptions,
|
||||
freeProducts,
|
||||
filteredProducts,
|
||||
} = useStore()
|
||||
|
||||
// const dialog = useDialog()
|
||||
|
||||
// -------------------- 业务逻辑 --------------------
|
||||
// 处理商业产品购买按钮点击
|
||||
const handleBuyProduct = () => {
|
||||
// 跳转到堡塔官网SSL证书购买页面
|
||||
window.open('https://www.bt.cn/new/ssl.html', '_blank')
|
||||
}
|
||||
|
||||
// 格式化价格显示
|
||||
const formatPrice = (price: number) => {
|
||||
return Math.floor(price)
|
||||
.toString()
|
||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 打开申请弹窗
|
||||
*/
|
||||
const handleOpenApplyModal = () => {
|
||||
useModal({
|
||||
title: $t(`申请免费证书 - Let's Encrypt`),
|
||||
area: '500px',
|
||||
component: CertificateForm,
|
||||
footer: true,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
test,
|
||||
handleTest,
|
||||
activeMainTab,
|
||||
activeTab,
|
||||
mainTabOptions,
|
||||
typeOptions,
|
||||
sslTypeList,
|
||||
sslTypeDescriptions,
|
||||
freeProducts,
|
||||
filteredProducts,
|
||||
handleBuyProduct,
|
||||
handleOpenApplyModal,
|
||||
formatPrice,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 证书申请表单控制器
|
||||
* @returns {object} 返回controller对象
|
||||
*/
|
||||
export const useCertificateFormController = () => {
|
||||
// 表单hooks
|
||||
const { useFormInput } = useFormHooks()
|
||||
const { addNewWorkflow } = useWorkflowViewStore()
|
||||
|
||||
// 加载遮罩
|
||||
const { open: openLoad, close: closeLoad } = useLoadingMask({ text: $t('t_6_1746667592831') })
|
||||
|
||||
// 消息和对话框
|
||||
const { confirm } = useModalHooks()
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
domains: '',
|
||||
provider_id: '',
|
||||
provider: '',
|
||||
})
|
||||
|
||||
// 表单配置
|
||||
const config = computed(() => [
|
||||
useFormInput($t('t_17_1745227838561'), 'domains'),
|
||||
{
|
||||
type: 'custom' as const,
|
||||
render: () => {
|
||||
return (
|
||||
<DnsProviderSelect
|
||||
type="dns"
|
||||
path="provider_id"
|
||||
value={formData.value.provider_id}
|
||||
onUpdate:value={(val: { value: string; type: string }) => {
|
||||
formData.value.provider_id = val.value
|
||||
formData.value.provider = val.type
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
/**
|
||||
* @description 表单验证规则
|
||||
*/
|
||||
const rules = {
|
||||
domains: {
|
||||
required: true,
|
||||
message: $t('t_7_1746667592468'),
|
||||
trigger: 'input',
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (!isDomain(value)) {
|
||||
callback(new Error($t('t_7_1746667592468')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
},
|
||||
provider_id: {
|
||||
required: true,
|
||||
message: $t('t_8_1746667591924'),
|
||||
trigger: 'change',
|
||||
type: 'string',
|
||||
},
|
||||
} as FormRules
|
||||
|
||||
/**
|
||||
* @description 提交表单
|
||||
*/
|
||||
const request = async () => {
|
||||
try {
|
||||
await addNewWorkflow({
|
||||
name: `申请免费证书-Let's Encrypt(${formData.value.domains})`,
|
||||
exec_type: 'manual',
|
||||
active: '1',
|
||||
content: JSON.stringify({
|
||||
id: 'start-1',
|
||||
name: '开始',
|
||||
type: 'start',
|
||||
config: { exec_type: 'manual' },
|
||||
childNode: {
|
||||
id: 'apply-1',
|
||||
name: '申请证书',
|
||||
type: 'apply',
|
||||
config: {
|
||||
...formData.value,
|
||||
email: 'test@test.com',
|
||||
end_day: 30,
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
} catch (error) {
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 使用表单hooks
|
||||
const { component, fetch } = useForm({
|
||||
config,
|
||||
defaultValue: formData,
|
||||
request,
|
||||
rules,
|
||||
})
|
||||
|
||||
// 关联确认按钮
|
||||
confirm(async (close) => {
|
||||
try {
|
||||
openLoad()
|
||||
await fetch()
|
||||
close()
|
||||
} catch (error) {
|
||||
return handleError(error)
|
||||
} finally {
|
||||
closeLoad()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
component,
|
||||
}
|
||||
}
|
||||
355
frontend/apps/allin-ssl/src/views/certApply/useStore.tsx
Normal file
355
frontend/apps/allin-ssl/src/views/certApply/useStore.tsx
Normal file
@@ -0,0 +1,355 @@
|
||||
export const useCertApplyStore = defineStore('cert-apply-store', () => {
|
||||
// -------------------- 状态定义 --------------------
|
||||
const test = ref('证书申请')
|
||||
|
||||
// 当前激活的主标签
|
||||
const activeMainTab = ref<'commercial' | 'free'>('commercial')
|
||||
|
||||
// 当前激活的子标签
|
||||
const activeTab = ref<'dv' | 'ov' | 'ev'>('dv')
|
||||
|
||||
// 主标签选项
|
||||
const mainTabOptions = ref([
|
||||
{ key: 'commercial', title: '商业证书', desc: '品牌SSL证书,安全保障,全球兼容' },
|
||||
{ key: 'free', title: '免费证书', desc: '适用于个人博客、测试环境的免费SSL证书' },
|
||||
])
|
||||
|
||||
// 证书类型选项
|
||||
const typeOptions = ref({
|
||||
dv: '域名型(DV)',
|
||||
ov: '企业型(OV)',
|
||||
ev: '增强型(EV)',
|
||||
})
|
||||
|
||||
// SSL证书类型列表
|
||||
const sslTypeList = ref([
|
||||
{ type: 'dv', title: '个人(DV 证书)', explain: '个人博客、个人项目等<br>可选择DV SSL证书。' },
|
||||
{
|
||||
type: 'ov',
|
||||
title: '传统行业(OV 证书)',
|
||||
explain: '企业官网、电商、教育、医疗、公共<br>部门等,可选择OV SSL证书。',
|
||||
},
|
||||
{
|
||||
type: 'ev',
|
||||
title: '金融机构(EV 证书)',
|
||||
explain: '银行、金融、保险、电子商务、中大型企<br>业、政府机关等,可选择EV SSL证书。',
|
||||
},
|
||||
])
|
||||
|
||||
// SSL证书类型详细说明
|
||||
const sslTypeDescriptions = ref({
|
||||
dv: {
|
||||
title: '域名型SSL证书 (DV SSL)',
|
||||
features: [
|
||||
'适用场景: 个人网站、博客、论坛等',
|
||||
'验证方式: 仅验证域名所有权',
|
||||
'签发时间: 最快5分钟',
|
||||
'安全级别: 基础级',
|
||||
],
|
||||
advantages: '优势: 价格低廉,签发速度快,适合个人使用',
|
||||
disadvantages: '劣势: 仅显示锁形图标,不显示企业信息',
|
||||
recommendation: '推荐指数: ★★★☆☆',
|
||||
},
|
||||
ov: {
|
||||
title: '企业型SSL证书 (OV SSL)',
|
||||
features: [
|
||||
'适用场景: 企业官网、电商网站、教育医疗网站等',
|
||||
'验证方式: 验证域名所有权和企业真实性',
|
||||
'签发时间: 1-3个工作日',
|
||||
'安全级别: 中级',
|
||||
],
|
||||
advantages: '优势: 兼顾安全和价格,适合一般企业使用',
|
||||
disadvantages: '劣势: 签发时间较DV长',
|
||||
recommendation: '推荐指数: ★★★★☆',
|
||||
},
|
||||
ev: {
|
||||
title: '增强型SSL证书 (EV SSL)',
|
||||
features: [
|
||||
'适用场景: 银行、金融机构、政府网站、大型企业',
|
||||
'验证方式: 最严格的身份验证流程',
|
||||
'签发时间: 5-7个工作日',
|
||||
'安全级别: 最高级',
|
||||
],
|
||||
advantages: '优势: 提供最高级别安全认证,浏览器地址栏显示企业名称',
|
||||
disadvantages: '劣势: 价格较高,签发时间最长',
|
||||
recommendation: '推荐指数: ★★★★★',
|
||||
},
|
||||
})
|
||||
|
||||
// 产品数据类型定义
|
||||
type ProductItem = {
|
||||
pid: number
|
||||
brand: string
|
||||
type: string
|
||||
add_price: number
|
||||
other_price: number
|
||||
title: string
|
||||
code: string
|
||||
num: number
|
||||
price: number
|
||||
discount: number
|
||||
state: number
|
||||
install_price: number
|
||||
src_price: number
|
||||
}
|
||||
|
||||
type ProductsType = {
|
||||
dv: ProductItem[]
|
||||
ov: ProductItem[]
|
||||
ev: ProductItem[]
|
||||
}
|
||||
|
||||
// 商业证书产品数据
|
||||
const products = ref<ProductsType>({
|
||||
dv: [
|
||||
{
|
||||
pid: 8001,
|
||||
brand: 'Positive',
|
||||
type: '域名型(DV)',
|
||||
add_price: 0,
|
||||
other_price: 398,
|
||||
title: 'PositiveSSL 单域名SSL证书',
|
||||
code: 'comodo-positivessl',
|
||||
num: 1,
|
||||
price: 159,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 150,
|
||||
src_price: 159,
|
||||
},
|
||||
{
|
||||
pid: 8002,
|
||||
brand: 'Positive',
|
||||
type: '域名型(DV)',
|
||||
add_price: 98,
|
||||
other_price: 1194,
|
||||
title: 'PositiveSSL 多域名SSL证书',
|
||||
code: 'comodo-positive-multi-domain',
|
||||
num: 3,
|
||||
price: 589,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 200,
|
||||
src_price: 589,
|
||||
},
|
||||
{
|
||||
pid: 8008,
|
||||
brand: 'Positive',
|
||||
type: '域名型(DV)',
|
||||
add_price: 0,
|
||||
other_price: 2100,
|
||||
title: 'PositiveSSL 通配符SSL证书',
|
||||
code: 'comodo-positivessl-wildcard',
|
||||
num: 1,
|
||||
price: 1289,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 200,
|
||||
src_price: 1289,
|
||||
},
|
||||
{
|
||||
pid: 8009,
|
||||
brand: 'Positive',
|
||||
type: '域名型(DV)',
|
||||
add_price: 880,
|
||||
other_price: 4500,
|
||||
title: 'PositiveSSL 多域名通配符SSL证书',
|
||||
code: 'comodo-positive-multi-domain-wildcard',
|
||||
num: 2,
|
||||
price: 3789,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 200,
|
||||
src_price: 3789,
|
||||
},
|
||||
],
|
||||
ov: [
|
||||
{
|
||||
pid: 8303,
|
||||
brand: 'Sectigo',
|
||||
type: '企业型(OV)',
|
||||
add_price: 0,
|
||||
other_price: 1880,
|
||||
title: 'Sectigo OV SSL证书',
|
||||
code: 'sectigo-ov',
|
||||
num: 1,
|
||||
price: 1388,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 1388,
|
||||
},
|
||||
{
|
||||
pid: 8304,
|
||||
brand: 'Sectigo',
|
||||
type: '企业型(OV)',
|
||||
add_price: 880,
|
||||
other_price: 5640,
|
||||
title: 'Sectigo OV多域名SSL证书',
|
||||
code: 'sectigo-ov-multi-san',
|
||||
num: 3,
|
||||
price: 3888,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 3888,
|
||||
},
|
||||
{
|
||||
pid: 8305,
|
||||
brand: 'Sectigo',
|
||||
type: '企业型(OV)',
|
||||
add_price: 0,
|
||||
other_price: 6980,
|
||||
title: 'Sectigo OV通配符SSL证书',
|
||||
code: 'sectigo-ov-wildcard',
|
||||
num: 1,
|
||||
price: 4888,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 4888,
|
||||
},
|
||||
{
|
||||
pid: 8307,
|
||||
brand: 'Sectigo',
|
||||
type: '企业型(OV)',
|
||||
add_price: 3680,
|
||||
other_price: 2094,
|
||||
title: 'Sectigo OV多域名通配符SSL证书',
|
||||
code: 'comodo-multi-domain-wildcard-certificate',
|
||||
num: 3,
|
||||
price: 15888,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 15888,
|
||||
},
|
||||
],
|
||||
ev: [
|
||||
{
|
||||
pid: 8300,
|
||||
brand: 'Sectigo',
|
||||
type: '企业增强型(EV)',
|
||||
add_price: 0,
|
||||
other_price: 3400,
|
||||
title: 'Sectigo EV SSL证书',
|
||||
code: 'comodo-ev-ssl-certificate',
|
||||
num: 1,
|
||||
price: 2788,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 2788,
|
||||
},
|
||||
{
|
||||
pid: 8302,
|
||||
brand: 'Sectigo',
|
||||
type: '企业增强型(EV)',
|
||||
add_price: 1488,
|
||||
other_price: 10200,
|
||||
title: 'Sectigo EV多域名SSL证书',
|
||||
code: 'comodo-ev-multi-domin-ssl',
|
||||
num: 3,
|
||||
price: 8388,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 8388,
|
||||
},
|
||||
{
|
||||
pid: 8520,
|
||||
brand: '锐安信',
|
||||
type: '企业增强型(EV)',
|
||||
add_price: 0,
|
||||
other_price: 3480,
|
||||
title: '锐安信EV SSL证书',
|
||||
code: 'ssltrus-ev-ssl',
|
||||
num: 1,
|
||||
price: 2688,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 2688,
|
||||
},
|
||||
{
|
||||
pid: 8521,
|
||||
brand: '锐安信',
|
||||
type: '企业增强型(EV)',
|
||||
add_price: 2380,
|
||||
other_price: 10440,
|
||||
title: '锐安信EV多域名SSL证书',
|
||||
code: 'ssltrus-ev-multi',
|
||||
num: 3,
|
||||
price: 9096,
|
||||
discount: 1,
|
||||
state: 1,
|
||||
install_price: 500,
|
||||
src_price: 9096,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
// 免费证书数据
|
||||
type FreeProductItem = {
|
||||
pid: number
|
||||
brand: string
|
||||
type: string
|
||||
title: string
|
||||
code: string
|
||||
num: number
|
||||
valid_days: number
|
||||
features: string[]
|
||||
}
|
||||
|
||||
const freeProducts = ref<FreeProductItem[]>([
|
||||
{
|
||||
pid: 9001,
|
||||
brand: "Let's Encrypt",
|
||||
type: '域名型(DV)',
|
||||
title: "Let's Encrypt 单域名SSL证书",
|
||||
code: 'letsencrypt-single',
|
||||
num: 1,
|
||||
valid_days: 90,
|
||||
features: ['90天有效期', '自动续期', '单域名', '全球认可'],
|
||||
},
|
||||
])
|
||||
|
||||
// -------------------- 派生状态 --------------------
|
||||
// 根据当前活动标签筛选产品
|
||||
const filteredProducts = computed(() => {
|
||||
if (activeMainTab.value === 'commercial') {
|
||||
return products.value[activeTab.value] || []
|
||||
} else {
|
||||
return [] // 免费证书不通过这个计算属性获取
|
||||
}
|
||||
})
|
||||
|
||||
// -------------------- 工具方法 --------------------
|
||||
const handleTest = () => {
|
||||
test.value = '点击了证书申请'
|
||||
}
|
||||
|
||||
return {
|
||||
test,
|
||||
handleTest,
|
||||
activeMainTab,
|
||||
activeTab,
|
||||
mainTabOptions,
|
||||
typeOptions,
|
||||
sslTypeList,
|
||||
sslTypeDescriptions,
|
||||
products,
|
||||
freeProducts,
|
||||
filteredProducts,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* useStore
|
||||
* @description 组合式API使用store
|
||||
* @returns {object} store - 返回store对象
|
||||
*/
|
||||
export const useStore = () => {
|
||||
const store = useCertApplyStore()
|
||||
return { ...store, ...storeToRefs(store) }
|
||||
}
|
||||
Reference in New Issue
Block a user