【初始化】前端工程项目

This commit is contained in:
chudong
2025-05-09 15:11:21 +08:00
parent c012704c9a
commit d7c556c3b0
524 changed files with 55595 additions and 112 deletions

View File

@@ -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>
)
},
})

View File

@@ -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" />
},
})

View File

@@ -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>
)
},
})

View File

@@ -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>
)
},
})

View 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>
)
},
})

View 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,
}
}

View 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) }
}