feat(core): 初始化项目基础架构和CMS功能模块
- 添加Docker相关配置文件(.dockerignore, .env.example, .gitignore) - 实现服务端API代理功能,支持文件、模块和服务器API转发 - 创建文章详情页、栏目文章列表页和单页内容展示页面 - 集成Ant Design Vue组件库并实现SSR样式提取功能 - 定义API响应数据结构类型和应用布局组件 - 开发开发者应用中心和文章管理页面 - 实现CMS导航菜单获取和多租户切换功能
This commit is contained in:
595
app/pages/create-app.vue
Normal file
595
app/pages/create-app.vue
Normal file
@@ -0,0 +1,595 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-screen-xl px-4 py-12">
|
||||
<a-typography-title :level="1" class="!mb-2">创建应用</a-typography-title>
|
||||
<a-typography-paragraph class="!text-gray-600 !mb-8">
|
||||
选择产品与时长,填写租户与绑定信息,生成订单并支付后自动开通产品并分配管理账号。
|
||||
</a-typography-paragraph>
|
||||
|
||||
<a-alert
|
||||
class="mb-6"
|
||||
type="info"
|
||||
show-icon
|
||||
message="当前页面已打通前端流程与接口对接点;如你的后端返回字段不同(订单号/二维码/账号信息),我可以按实际接口再调整。"
|
||||
/>
|
||||
|
||||
<a-steps :current="step" class="mb-8">
|
||||
<a-step title="选择产品" />
|
||||
<a-step title="选择时长" />
|
||||
<a-step title="填写信息" />
|
||||
<a-step title="生成订单" />
|
||||
<a-step title="支付订单" />
|
||||
<a-step title="开通交付" />
|
||||
</a-steps>
|
||||
|
||||
<a-card v-if="step === 0" title="选择产品">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col v-for="p in products" :key="p.code" :xs="24" :md="12" :lg="8">
|
||||
<a-card
|
||||
hoverable
|
||||
:class="selectedProduct?.code === p.code ? 'card-active' : ''"
|
||||
@click="selectProduct(p)"
|
||||
>
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span>{{ p.name }}</span>
|
||||
<a-tag v-if="p.recommend" color="green">推荐</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<a-typography-paragraph class="!text-gray-600">
|
||||
{{ p.desc }}
|
||||
</a-typography-paragraph>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a-tag v-for="t in p.tags" :key="t">{{ t }}</a-tag>
|
||||
</div>
|
||||
<div class="mt-4 text-sm text-gray-500">
|
||||
起步价:¥{{ p.pricePerMonth }}/月
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<a-button type="primary" :disabled="!selectedProduct" @click="next()">下一步</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card v-else-if="step === 1" title="选择时长">
|
||||
<a-row :gutter="[16, 16]">
|
||||
<a-col :xs="24" :lg="14">
|
||||
<a-segmented v-model:value="durationMonths" :options="durationOptions" block />
|
||||
<div class="mt-6">
|
||||
<a-descriptions bordered size="small" :column="1">
|
||||
<a-descriptions-item label="产品">{{ selectedProduct?.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="购买时长">{{ durationMonths }} 个月</a-descriptions-item>
|
||||
<a-descriptions-item label="单价">¥{{ selectedProduct?.pricePerMonth }}/月</a-descriptions-item>
|
||||
<a-descriptions-item label="应付金额">
|
||||
<span class="text-lg font-semibold text-green-600">¥{{ priceTotal }}</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :xs="24" :lg="10">
|
||||
<a-card title="支持加购" size="small">
|
||||
<a-list size="small" :data-source="addons">
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>{{ item }}</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<div class="mt-6 flex justify-between">
|
||||
<a-button @click="prev()">上一步</a-button>
|
||||
<a-button type="primary" @click="next()">下一步</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card v-else-if="step === 2" title="填写租户与绑定信息">
|
||||
<a-form ref="formRef" layout="vertical" :model="form" :rules="rules" @finish="next">
|
||||
<a-row :gutter="16">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="租户名称" name="tenantName">
|
||||
<a-input v-model:value="form.tenantName" placeholder="例如:某某科技有限公司" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="绑定域名" name="domain">
|
||||
<a-input v-model:value="form.domain" placeholder="例如:example.com" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input v-model:value="form.email" placeholder="用于接收交付信息" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input v-model:value="form.phone" placeholder="用于短信验证与管理员账号" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col :xs="24" :md="12">
|
||||
<a-form-item label="短信验证码" name="smsCode">
|
||||
<a-input v-model:value="form.smsCode" placeholder="请输入验证码" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12" class="flex items-center">
|
||||
<a-button :disabled="smsCountdown > 0" :loading="smsSending" @click="onSendSms">
|
||||
{{ smsCountdown > 0 ? `${smsCountdown}s 后重试` : '发送验证码' }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-alert
|
||||
class="mb-4"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="短信验证码接口复用登录短信验证码(sendSmsCaptcha)。如你有专用的开通验证码接口,可替换。"
|
||||
/>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<a-button @click="prev()">上一步</a-button>
|
||||
<a-button type="primary" html-type="submit">下一步</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card v-else-if="step === 3" title="生成订单">
|
||||
<a-descriptions bordered :column="1">
|
||||
<a-descriptions-item label="产品">{{ selectedProduct?.name }}</a-descriptions-item>
|
||||
<a-descriptions-item label="购买时长">{{ durationMonths }} 个月</a-descriptions-item>
|
||||
<a-descriptions-item label="租户名称">{{ form.tenantName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定域名">{{ form.domain }}</a-descriptions-item>
|
||||
<a-descriptions-item label="邮箱">{{ form.email }}</a-descriptions-item>
|
||||
<a-descriptions-item label="手机号">{{ form.phone }}</a-descriptions-item>
|
||||
<a-descriptions-item label="应付金额">
|
||||
<span class="text-lg font-semibold text-green-600">¥{{ priceTotal }}</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-alert
|
||||
class="mt-4"
|
||||
type="info"
|
||||
show-icon
|
||||
message="点击“生成订单”后将创建订单并请求微信 Native 支付二维码。"
|
||||
/>
|
||||
|
||||
<div class="mt-6 flex justify-between">
|
||||
<a-button @click="prev()">上一步</a-button>
|
||||
<a-button type="primary" :loading="creatingOrder" @click="createOrderAndPay">生成订单</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card v-else-if="step === 4" title="支付订单">
|
||||
<a-row :gutter="[24, 24]">
|
||||
<a-col :xs="24" :lg="12">
|
||||
<a-descriptions bordered size="small" :column="1">
|
||||
<a-descriptions-item label="订单号">{{ order?.orderNo || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="金额">¥{{ order?.payPrice || priceTotal }}</a-descriptions-item>
|
||||
<a-descriptions-item label="支付方式">微信 Native(扫码支付)</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-2">
|
||||
<a-button :loading="checkingPay" @click="checkPayStatus">查询支付状态</a-button>
|
||||
<a-button @click="rebuildPayCode" :disabled="!order">重新获取二维码</a-button>
|
||||
<a-button danger @click="resetAll">取消并重来</a-button>
|
||||
</div>
|
||||
|
||||
<a-alert
|
||||
class="mt-6"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="支付成功后点击“查询支付状态”,确认到账后自动进入开通交付。"
|
||||
/>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :lg="12">
|
||||
<a-card title="扫码支付" size="small">
|
||||
<div class="flex items-center justify-center py-6">
|
||||
<a-qrcode v-if="payCodeUrl" :value="payCodeUrl" :size="220" />
|
||||
<a-empty v-else description="暂无二维码" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-card v-else-if="step === 5" title="开通交付">
|
||||
<a-result
|
||||
v-if="provisioned"
|
||||
status="success"
|
||||
title="开通成功"
|
||||
sub-title="租户已创建并完成初始化,可使用管理员账号登录后台。"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="navigateTo('/contact')">获取后台地址</a-button>
|
||||
<a-button @click="resetAll">再创建一个</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
|
||||
<a-alert
|
||||
v-else
|
||||
type="info"
|
||||
show-icon
|
||||
message="正在开通中..."
|
||||
/>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<a-descriptions bordered size="small" :column="1">
|
||||
<a-descriptions-item label="TenantId">
|
||||
{{ provisionInfo?.user?.tenantId ?? '-' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="租户名称">{{ form.tenantName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="绑定域名">{{ form.domain }}</a-descriptions-item>
|
||||
<a-descriptions-item label="管理员账号">
|
||||
{{ provisionInfo?.user?.username || form.phone }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="初始密码">
|
||||
{{ adminPasswordHint }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Access Token">
|
||||
<a-typography-text
|
||||
v-if="provisionInfo?.access_token"
|
||||
:copyable="{ text: provisionInfo.access_token }"
|
||||
>
|
||||
点击复制
|
||||
</a-typography-text>
|
||||
<span v-else>-</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { usePageSeo } from '@/composables/usePageSeo'
|
||||
import { sendSmsCaptcha } from '@/api/passport/login'
|
||||
import { createWithOrder, getNativeCode, type PaymentCreateResult } from '@/api/system/payment'
|
||||
import { getOrder } from '@/api/system/order'
|
||||
import request from '@/utils/request'
|
||||
import { SERVER_API_URL } from '@/config/setting'
|
||||
import type { ApiResult } from '@/api'
|
||||
import type { Order } from '@/api/system/order/model'
|
||||
|
||||
type Product = {
|
||||
code: string
|
||||
name: string
|
||||
desc: string
|
||||
tags: string[]
|
||||
pricePerMonth: number
|
||||
recommend?: boolean
|
||||
}
|
||||
|
||||
usePageSeo({
|
||||
title: '创建应用 - 选品/支付/自动开通',
|
||||
description:
|
||||
'选择产品与时长,填写租户信息并短信验证,生成订单并支付后自动创建租户、初始化模块与数据并交付管理账号。',
|
||||
path: '/create-app'
|
||||
})
|
||||
|
||||
const step = ref(0)
|
||||
|
||||
const products: Product[] = [
|
||||
{
|
||||
code: 'website',
|
||||
name: '企业官网',
|
||||
desc: '品牌展示与获客转化,支持模板、SEO 与可视化配置。',
|
||||
tags: ['模板', 'SEO', '多语言'],
|
||||
pricePerMonth: 199,
|
||||
recommend: true
|
||||
},
|
||||
{
|
||||
code: 'shop',
|
||||
name: '电商系统',
|
||||
desc: '商品/订单/支付/营销基础能力,插件化扩展。',
|
||||
tags: ['支付', '插件', '营销'],
|
||||
pricePerMonth: 399,
|
||||
recommend: true
|
||||
},
|
||||
{
|
||||
code: 'mp',
|
||||
name: '小程序/公众号',
|
||||
desc: '多端渠道接入与统一管理,适配内容与电商场景。',
|
||||
tags: ['多端', '渠道'],
|
||||
pricePerMonth: 299
|
||||
}
|
||||
]
|
||||
|
||||
const selectedProduct = ref<Product | null>(null)
|
||||
const durationMonths = ref(12)
|
||||
const durationOptions = [
|
||||
{ label: '1个月', value: 1 },
|
||||
{ label: '3个月', value: 3 },
|
||||
{ label: '12个月', value: 12 },
|
||||
{ label: '24个月', value: 24 }
|
||||
]
|
||||
|
||||
const addons = ['模板加购(示例)', '插件加购(示例)', '私有化交付(示例)']
|
||||
|
||||
const priceTotal = computed(() => {
|
||||
const base = selectedProduct.value?.pricePerMonth || 0
|
||||
return base * Number(durationMonths.value || 0)
|
||||
})
|
||||
|
||||
const formRef = ref()
|
||||
const form = reactive({
|
||||
tenantName: '',
|
||||
domain: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
smsCode: ''
|
||||
})
|
||||
|
||||
function isDomainLike(v: string) {
|
||||
const value = v.trim().toLowerCase()
|
||||
return /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$/.test(value)
|
||||
}
|
||||
|
||||
function isPhoneLike(v: string) {
|
||||
const value = v.trim()
|
||||
return /^1\d{10}$/.test(value)
|
||||
}
|
||||
|
||||
function isSmsCodeLike(v: string) {
|
||||
const value = v.trim()
|
||||
return /^\d{4,8}$/.test(value)
|
||||
}
|
||||
|
||||
const rules = {
|
||||
tenantName: [{ required: true, message: '请填写租户名称' }],
|
||||
domain: [
|
||||
{ required: true, message: '请填写绑定域名' },
|
||||
{ validator: (_: unknown, v: string) => (isDomainLike(v) ? Promise.resolve() : Promise.reject(new Error('域名格式不正确'))) }
|
||||
],
|
||||
email: [{ required: true, type: 'email', message: '请填写正确邮箱' }],
|
||||
phone: [
|
||||
{ required: true, message: '请填写手机号' },
|
||||
{ validator: (_: unknown, v: string) => (isPhoneLike(v) ? Promise.resolve() : Promise.reject(new Error('手机号格式不正确'))) }
|
||||
],
|
||||
smsCode: [
|
||||
{ required: true, message: '请填写短信验证码' },
|
||||
{ validator: (_: unknown, v: string) => (isSmsCodeLike(v) ? Promise.resolve() : Promise.reject(new Error('验证码格式不正确'))) }
|
||||
]
|
||||
}
|
||||
|
||||
const smsSending = ref(false)
|
||||
const smsCountdown = ref(0)
|
||||
let countdownTimer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
async function onSendSms() {
|
||||
if (!form.phone) {
|
||||
message.warning('请先填写手机号')
|
||||
return
|
||||
}
|
||||
smsSending.value = true
|
||||
try {
|
||||
await sendSmsCaptcha({ phone: form.phone })
|
||||
message.success('验证码已发送')
|
||||
smsCountdown.value = 60
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
countdownTimer = setInterval(() => {
|
||||
smsCountdown.value -= 1
|
||||
if (smsCountdown.value <= 0) {
|
||||
smsCountdown.value = 0
|
||||
if (countdownTimer) clearInterval(countdownTimer)
|
||||
countdownTimer = undefined
|
||||
}
|
||||
}, 1000)
|
||||
} catch (e) {
|
||||
message.error(String(e))
|
||||
} finally {
|
||||
smsSending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function selectProduct(p: Product) {
|
||||
selectedProduct.value = p
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (step.value < 5) step.value += 1
|
||||
}
|
||||
function prev() {
|
||||
if (step.value > 0) step.value -= 1
|
||||
}
|
||||
|
||||
const creatingOrder = ref(false)
|
||||
const order = ref<Order | null>(null)
|
||||
const payCodeUrl = ref<string>('')
|
||||
const payment = ref<PaymentCreateResult | null>(null)
|
||||
|
||||
function pickFirstString(obj: unknown, keys: string[]) {
|
||||
if (!obj || typeof obj !== 'object') return ''
|
||||
const record = obj as Record<string, unknown>
|
||||
for (const key of keys) {
|
||||
const value = record[key]
|
||||
if (typeof value === 'string' && value) return value
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
async function createOrderAndPay() {
|
||||
if (!selectedProduct.value) return
|
||||
creatingOrder.value = true
|
||||
try {
|
||||
const orderInfo: Partial<Order> & Record<string, unknown> = {
|
||||
type: 0,
|
||||
channel: 0,
|
||||
realName: form.tenantName,
|
||||
phone: form.phone,
|
||||
totalNum: durationMonths.value,
|
||||
totalPrice: String(priceTotal.value),
|
||||
payPrice: String(priceTotal.value),
|
||||
comments: JSON.stringify({
|
||||
product: selectedProduct.value.code,
|
||||
months: durationMonths.value,
|
||||
tenantName: form.tenantName,
|
||||
domain: form.domain,
|
||||
email: form.email
|
||||
})
|
||||
}
|
||||
|
||||
const unifiedPayload = {
|
||||
paymentChannel: 'WECHAT_NATIVE',
|
||||
paymentType: 1,
|
||||
amount: priceTotal.value,
|
||||
subject: `${selectedProduct.value.name}(${durationMonths.value}个月)`,
|
||||
description: `租户:${form.tenantName};域名:${form.domain}`,
|
||||
goodsId: selectedProduct.value.code,
|
||||
quantity: 1,
|
||||
orderType: 0,
|
||||
buyerRemarks: orderInfo.comments,
|
||||
extraParams: {
|
||||
product: selectedProduct.value.code,
|
||||
months: durationMonths.value,
|
||||
tenantName: form.tenantName,
|
||||
domain: form.domain,
|
||||
email: form.email,
|
||||
phone: form.phone
|
||||
},
|
||||
order: orderInfo
|
||||
}
|
||||
|
||||
const data = await createWithOrder(unifiedPayload)
|
||||
payment.value = data || null
|
||||
|
||||
const orderFromApi = (data as any)?.order || (data as any)?.orderInfo || (data as any)?.orderDTO
|
||||
order.value = {
|
||||
...(orderFromApi || {}),
|
||||
orderId: (data as any)?.orderId ?? orderFromApi?.orderId,
|
||||
orderNo: (data as any)?.orderNo ?? orderFromApi?.orderNo,
|
||||
payPrice: (orderFromApi || {})?.payPrice ?? String(priceTotal.value)
|
||||
} as Order
|
||||
|
||||
payCodeUrl.value =
|
||||
pickFirstString(data, ['codeUrl', 'url', 'payUrl', 'paymentUrl', 'qrcode']) ||
|
||||
pickFirstString(order.value, ['qrcode'])
|
||||
|
||||
if (!payCodeUrl.value && order.value?.orderId) {
|
||||
await rebuildPayCode()
|
||||
}
|
||||
if (!payCodeUrl.value) {
|
||||
message.warning('后端未返回二维码地址(codeUrl/url/payUrl),请确认统一下单接口返回格式或提供支付二维码接口')
|
||||
}
|
||||
step.value = 4
|
||||
} catch (e) {
|
||||
message.error(String(e))
|
||||
} finally {
|
||||
creatingOrder.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function rebuildPayCode() {
|
||||
if (!order.value) return
|
||||
try {
|
||||
const data = await getNativeCode(order.value)
|
||||
payCodeUrl.value = pickFirstString(data, ['codeUrl', 'url', 'payUrl', 'paymentUrl', 'qrcode']) || String(data || '')
|
||||
if (!payCodeUrl.value) {
|
||||
message.warning('后端未返回二维码地址(codeUrl/url),请确认接口返回格式')
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(String(e))
|
||||
}
|
||||
}
|
||||
|
||||
const checkingPay = ref(false)
|
||||
const provisioned = ref(false)
|
||||
const adminPasswordHint = '初始密码将通过短信/邮件发送(或由客服提供)'
|
||||
|
||||
async function checkPayStatus() {
|
||||
if (!order.value?.orderId) {
|
||||
message.warning('缺少订单ID,暂无法查询支付状态(请确认统一下单接口是否返回 orderId,或提供按 orderNo/paymentNo 查询的接口)')
|
||||
return
|
||||
}
|
||||
checkingPay.value = true
|
||||
try {
|
||||
const latest = await getOrder(order.value.orderId)
|
||||
order.value = latest
|
||||
if (Number(latest.payStatus) === 1) {
|
||||
message.success('已支付,开始开通...')
|
||||
step.value = 5
|
||||
await provision()
|
||||
} else {
|
||||
message.info('订单未支付或未到账,请稍后重试')
|
||||
}
|
||||
} catch (e) {
|
||||
message.error(String(e))
|
||||
} finally {
|
||||
checkingPay.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function provision() {
|
||||
try {
|
||||
const payload = {
|
||||
websiteName: form.tenantName,
|
||||
domain: form.domain,
|
||||
email: form.email,
|
||||
phone: form.phone,
|
||||
username: form.phone,
|
||||
smsCode: form.smsCode,
|
||||
code: form.smsCode,
|
||||
comments: JSON.stringify({
|
||||
product: selectedProduct.value?.code,
|
||||
months: durationMonths.value,
|
||||
orderNo: order.value?.orderNo
|
||||
})
|
||||
}
|
||||
|
||||
const res = await request.post<ApiResult<unknown>>(SERVER_API_URL + '/superAdminRegister', payload)
|
||||
if (res.data.code !== 0) throw new Error(res.data.message || '开通失败')
|
||||
provisionInfo.value = (res.data.data || null) as any
|
||||
provisioned.value = true
|
||||
} catch (e) {
|
||||
provisioned.value = false
|
||||
message.error(String(e))
|
||||
}
|
||||
}
|
||||
|
||||
type ProvisionUser = {
|
||||
tenantId?: number
|
||||
tenantName?: string | null
|
||||
username?: string
|
||||
phone?: string
|
||||
email?: string | null
|
||||
} & Record<string, unknown>
|
||||
|
||||
type ProvisionInfo = {
|
||||
access_token?: string
|
||||
user?: ProvisionUser
|
||||
} & Record<string, unknown>
|
||||
|
||||
const provisionInfo = ref<ProvisionInfo | null>(null)
|
||||
|
||||
function resetAll() {
|
||||
step.value = 0
|
||||
selectedProduct.value = null
|
||||
durationMonths.value = 12
|
||||
form.tenantName = ''
|
||||
form.domain = ''
|
||||
form.email = ''
|
||||
form.phone = ''
|
||||
form.smsCode = ''
|
||||
order.value = null
|
||||
payCodeUrl.value = ''
|
||||
payment.value = null
|
||||
provisioned.value = false
|
||||
provisionInfo.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card-active {
|
||||
border-color: #16a34a;
|
||||
box-shadow: 0 0 0 2px rgba(22, 163, 74, 0.15);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user