feat: 添加云端函数HTTP请求、SMTP邮件、数据库操作能力

- 添加HTTP客户端工具支持GET/POST请求
- 添加SMTP邮件发送功能
- 添加数据库操作(db.getRecords, db.deleteRecord, db.deleteRecords)
- 添加订单管理云端函数(读取、接收、筛选、成功、失败)
- 添加测试推送和邮件通知云端函数
- 修复云端函数编辑页面预览代码换行显示
- 云端变量数据模型重构(合并数据类型)
This commit is contained in:
2026-04-30 20:54:02 +08:00
parent a4df9e91a0
commit 10044b474f
46 changed files with 15249 additions and 250 deletions
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { Boxes, Code, Database, DollarSign, FileLock, Gauge, GitBranch, Hash, Key, Megaphone, MessageSquare, Network, Plug, ScrollText, Shield, Users, Variable } from 'lucide-vue-next'
import { Boxes, Code, DollarSign, FileLock, Gauge, GitBranch, Hash, Key, Megaphone, MessageSquare, Network, Plug, ScrollText, Shield, Users, Variable } from 'lucide-vue-next'
import { onMounted, reactive } from 'vue'
import NavTeam from '@/components/app-sidebar/nav-team.vue'
@@ -1,11 +1,4 @@
<script lang="ts" setup>
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb'
import { cn } from '@/lib/utils'
import type { LayoutHeaderProps } from './types'
@@ -77,7 +77,7 @@ defineExpose({
type="search"
:placeholder="t('developer.agents.searchPlaceholder')"
class="h-9 w-[250px] pl-8"
@update:model-value="emit('update:searchFilter', $event)"
@update:model-value="emit('update:searchFilter', String($event))"
/>
</div>
<slot name="filters" />
+1 -1
View File
@@ -18,7 +18,7 @@ const router = useRouter()
const loading = ref(true)
const agents = ref<Agent[]>([])
const tableRef = ref()
const tableRef = ref<InstanceType<typeof DataTable> | null>(null)
const searchFilter = ref('')
const statusFilter = ref<string>('')
const viewMode = ref<'tree' | 'list'>('tree')
@@ -71,9 +71,9 @@ const testEmail = ref('')
const sendCodeEmail = ref('')
const sendCodePurpose = ref('register')
const canUseEmailVerify = computed(() => config.value?.permission?.allow_email_verify)
const _canUseEmailVerify = computed(() => config.value?.permission?.allow_email_verify)
const canUsePasswordReset = computed(() => config.value?.permission?.allow_password_reset)
const canUseCustomSMTP = computed(() => config.value?.permission?.allow_custom_smtp)
const _canUseCustomSMTP = computed(() => config.value?.permission?.allow_custom_smtp)
const canUseCustomTemplate = computed(() => config.value?.permission?.allow_custom_template)
async function fetchConfig() {
@@ -0,0 +1,53 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { useRouter } from 'vue-router'
import type { App } from '../data/schema'
const props = defineProps<{
app: App
}>()
const emit = defineEmits<{
refresh: []
}>()
const router = useRouter()
function formatDate(date: string | Date) {
return new Date(date).toLocaleDateString('zh-CN')
}
</script>
<template>
<UiCard class="cursor-pointer hover:border-primary/50 transition-colors" @click="router.push(`/admin/applications/${props.app.id}`)">
<UiCardHeader class="pb-3">
<div class="flex items-start justify-between">
<div class="flex items-center gap-3">
<div class="size-10 rounded-lg bg-primary/10 flex items-center justify-center">
<Icon v-if="props.app.icon_url" :icon="props.app.icon_url" class="size-6 text-primary" />
<Icon v-else icon="lucide:box" class="size-6 text-primary" />
</div>
<div>
<UiCardTitle class="text-base">
{{ props.app.name }}
</UiCardTitle>
<p class="text-sm text-muted-foreground line-clamp-1">
{{ props.app.description || '暂无描述' }}
</p>
</div>
</div>
<UiBadge :variant="props.app.status === 'active' ? 'default' : 'secondary'">
{{ props.app.status === 'active' ? '正常' : '停用' }}
</UiBadge>
</div>
</UiCardHeader>
<UiCardContent>
<div class="flex items-center justify-between text-sm text-muted-foreground">
<span>用户: {{ props.app.users || 0 }}</span>
<span>验证: {{ props.app.verify_count || 0 }}</span>
<span>{{ formatDate(props.app.created_at) }}</span>
</div>
</UiCardContent>
</UiCard>
</template>
@@ -27,7 +27,6 @@ const form = ref({
key: '',
default_value: '',
var_type: 'string',
data_type: 'single',
max_records: 0,
scope: 'app',
write_permission: 'admin',
@@ -49,7 +48,7 @@ async function fetchVariable() {
loading.value = true
try {
const varId = route.params.id
const data = await api.get<{ id: number, app_id: number, key: string, default_value: string, var_type: string, data_type: string, max_records: number, scope: string, write_permission: string, description: string, status: string }>(`/dev/cloud-variables/${varId}`)
const data = await api.get<{ id: number, app_id: number, key: string, default_value: string, var_type: string, max_records: number, scope: string, write_permission: string, description: string, status: string }>(`/dev/cloud-variables/${varId}`)
const variable = data
if (variable) {
form.value = {
@@ -58,7 +57,6 @@ async function fetchVariable() {
key: variable.key,
default_value: variable.default_value || '',
var_type: variable.var_type || 'string',
data_type: variable.data_type || 'single',
max_records: variable.max_records || 0,
scope: variable.scope || 'app',
write_permission: variable.write_permission || 'admin',
@@ -100,7 +98,6 @@ async function handleSave() {
key: form.value.key,
default_value: form.value.default_value,
var_type: form.value.var_type,
data_type: form.value.data_type,
max_records: form.value.max_records,
write_permission: form.value.write_permission,
description: form.value.description,
@@ -176,47 +173,14 @@ onMounted(async () => {
<UiSelectItem value="string">
{{ t('developer.cloudVariables.types.string') }}
</UiSelectItem>
<UiSelectItem value="stream">
记录
</UiSelectItem>
</UiSelectContent>
</UiSelect>
</div>
<div class="space-y-2">
<UiLabel>数据模式</UiLabel>
<UiRadioGroup v-model="form.data_type" class="grid grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
:class="form.data_type === 'single' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.data_type = 'single'"
>
<UiRadioGroupItem value="single" class="mt-0.5" />
<div>
<p class="font-medium text-sm">
单值模式
</p>
<p class="text-xs text-muted-foreground mt-1">
存储单个值适合配置项
</p>
</div>
</div>
<div
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
:class="form.data_type === 'stream' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.data_type = 'stream'"
>
<UiRadioGroupItem value="stream" class="mt-0.5" />
<div>
<p class="font-medium text-sm">
流水模式
</p>
<p class="text-xs text-muted-foreground mt-1">
存储记录列表适合订单日志等
</p>
</div>
</div>
</UiRadioGroup>
</div>
<div v-if="form.data_type === 'stream'" class="space-y-2">
<div v-if="form.var_type === 'stream'" class="space-y-2">
<UiLabel for="maxRecords">
最大记录数
</UiLabel>
@@ -334,7 +298,7 @@ onMounted(async () => {
</div>
<div
v-if="form.data_type === 'stream'"
v-if="form.var_type === 'stream'"
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
:class="form.write_permission === 'app_user' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'app_user'"
@@ -375,13 +339,7 @@ onMounted(async () => {
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.type') }}</span>
<span>{{ t(`developer.cloudVariables.types.${form.var_type}`) }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">数据模式</span>
<UiBadge variant="outline">
{{ form.data_type === 'stream' ? '流水' : '单值' }}
</UiBadge>
</div>
<div v-if="form.data_type === 'stream'" class="flex justify-between text-sm">
<div v-if="form.var_type === 'stream'" class="flex justify-between text-sm">
<span class="text-muted-foreground">最大记录数</span>
<span>{{ form.max_records || '不限制' }}</span>
</div>
@@ -10,12 +10,12 @@ import api from '@/services/api'
interface Variable {
id: number
key: string
data_type: string
var_type: string
scope: string
max_records: number
}
interface Record {
interface VariableRecord {
id: number
data: Record<string, any>
user_id?: number
@@ -31,7 +31,7 @@ const router = useRouter()
const loading = ref(true)
const variable = ref<Variable | null>(null)
const records = ref<Record[]>([])
const records = ref<VariableRecord[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
@@ -44,8 +44,8 @@ async function fetchVariable() {
const data = await api.get<{ variables: Variable[] }>(`/dev/cloud-variables?app_id=${route.query.app_id}`)
const vars = data?.variables || []
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
if (variable.value && variable.value.data_type !== 'stream') {
toast.error('该变量不是流水类型')
if (variable.value && variable.value.var_type !== 'stream') {
toast.error('该变量不是记录类型')
router.back()
}
}
@@ -68,7 +68,7 @@ async function fetchRecords() {
if (endDate.value)
params.append('end_date', endDate.value)
const data = await api.get(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
const data = await api.get<{ records: VariableRecord[], total: number }>(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
records.value = data?.records || []
total.value = data?.total || 0
}
@@ -141,11 +141,11 @@ onMounted(async () => {
<template>
<BasicPage
title="流水记录"
description="查看云端变量的流水记录数据"
title="变量记录"
description="查看云端变量的记录数据"
:breadcrumbs="[
{ title: '云端变量', href: '/developer/cloud-variables' },
{ title: '流水记录' },
{ title: '变量记录' },
]"
>
<div class="space-y-6">
@@ -21,6 +21,7 @@ export function getColumns(actions: {
onDelete: (row: CloudVariable) => void
onToggleStatus: (row: CloudVariable) => void
onDownload: (row: CloudVariable) => void
onViewRecords?: (row: CloudVariable) => void
}, t: Composer['t']): ColumnDef<CloudVariable>[] {
return [
{
@@ -86,6 +87,7 @@ export function getColumns(actions: {
decimal: t('developer.cloudVariables.types.decimal'),
string: t('developer.cloudVariables.types.string'),
binary: t('developer.cloudVariables.types.binary'),
stream: '记录',
}
return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string')
},
@@ -36,7 +36,7 @@ function viewRecords() {
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="end" class="w-[160px]">
<UiDropdownMenuItem v-if="variable.data_type === 'stream'" @click="viewRecords">
<UiDropdownMenuItem v-if="variable.var_type === 'stream'" @click="viewRecords">
<List class="mr-2 h-4 w-4" />
查看记录
</UiDropdownMenuItem>
@@ -27,7 +27,6 @@ const form = ref({
key: '',
default_value: '',
var_type: 'string',
data_type: 'single',
max_records: 0,
scope: 'app',
write_permission: 'admin',
@@ -95,7 +94,6 @@ async function handleSave() {
key: form.value.key,
default_value: form.value.default_value,
var_type: form.value.var_type,
data_type: form.value.data_type,
max_records: form.value.max_records,
scope: form.value.scope,
write_permission: form.value.write_permission,
@@ -215,47 +213,14 @@ onMounted(() => {
<UiSelectItem value="binary">
二进制
</UiSelectItem>
<UiSelectItem value="stream">
记录
</UiSelectItem>
</UiSelectContent>
</UiSelect>
</div>
<div class="space-y-2">
<UiLabel>数据模式</UiLabel>
<UiRadioGroup v-model="form.data_type" class="grid grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
:class="form.data_type === 'single' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.data_type = 'single'"
>
<UiRadioGroupItem value="single" class="mt-0.5" />
<div>
<p class="font-medium text-sm">
单值模式
</p>
<p class="text-xs text-muted-foreground mt-1">
存储单个值适合配置项
</p>
</div>
</div>
<div
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
:class="form.data_type === 'stream' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.data_type = 'stream'"
>
<UiRadioGroupItem value="stream" class="mt-0.5" />
<div>
<p class="font-medium text-sm">
流水模式
</p>
<p class="text-xs text-muted-foreground mt-1">
存储记录列表适合订单日志等
</p>
</div>
</div>
</UiRadioGroup>
</div>
<div v-if="form.data_type === 'stream'" class="space-y-2">
<div v-if="form.var_type === 'stream'" class="space-y-2">
<UiLabel for="maxRecords">
最大记录数
</UiLabel>
@@ -428,7 +393,7 @@ onMounted(() => {
</div>
<div
v-if="form.data_type === 'stream'"
v-if="form.var_type === 'stream'"
class="flex items-start gap-3 p-4 rounded-lg border cursor-pointer transition-colors"
:class="form.write_permission === 'app_user' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.write_permission = 'app_user'"
@@ -469,13 +434,7 @@ onMounted(() => {
<span class="text-muted-foreground">{{ t('developer.cloudVariables.create.type') }}</span>
<span>{{ t(`developer.cloudVariables.types.${form.var_type}`) }}</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-muted-foreground">数据模式</span>
<UiBadge variant="outline">
{{ form.data_type === 'stream' ? '流水' : '单值' }}
</UiBadge>
</div>
<div v-if="form.data_type === 'stream'" class="flex justify-between text-sm">
<div v-if="form.var_type === 'stream'" class="flex justify-between text-sm">
<span class="text-muted-foreground">最大记录数</span>
<span>{{ form.max_records || '不限制' }}</span>
</div>
@@ -8,7 +8,6 @@ export const cloudVariableSchema = z.object({
key: z.string(),
default_value: z.string().optional().nullable(),
var_type: z.string().optional().nullable(),
data_type: z.string().optional().nullable(),
max_records: z.union([z.string(), z.number()]).optional().nullable(),
file_path: z.string().optional().nullable(),
file_size: z.union([z.string(), z.number()]).optional().nullable(),
@@ -268,9 +268,7 @@ onMounted(() => {
<div class="text-muted-foreground mb-2">
{{ t('developer.cloudFunction.create.code') }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
{{ form.code || '-' }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre-wrap;">{{ form.code || '-' }}</div>
</div>
</div>
</div>
@@ -221,9 +221,7 @@ onMounted(() => {
<div class="text-muted-foreground mb-2">
{{ t('developer.cloudFunction.create.code') }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
{{ form.code || '-' }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre-wrap;">{{ form.code || '-' }}</div>
</div>
</div>
</div>
+1 -1
View File
@@ -14,7 +14,7 @@ const cardTypes = ref<any[]>([])
onMounted(async () => {
const appId = route.params.id
try {
const data = await api.get(`/agent/apps/${appId}`)
const data = await api.get<{ app: any, cardTypes: any[] }>(`/agent/apps/${appId}`)
app.value = data?.app
cardTypes.value = data?.cardTypes || []
}
+3 -3
View File
@@ -19,7 +19,7 @@ const form = ref({
quantity: 1,
})
const selectedApp = computed(() => {
const _selectedApp = computed(() => {
return apps.value.find(a => a.id === Number(form.value.app_id))
})
@@ -34,11 +34,11 @@ const totalPrice = computed(() => {
onMounted(async () => {
try {
const data = await api.get('/agent/apps')
const data = await api.get<any[]>('/agent/apps')
apps.value = data || []
if (apps.value.length > 0) {
const typesData = await api.get('/agent/card-types')
const typesData = await api.get<any[]>('/agent/card-types')
cardTypes.value = typesData || []
}
}
+1 -1
View File
@@ -30,7 +30,7 @@ onMounted(async () => {
}
})
function formatDate(dateStr: string) {
function formatDate(dateStr: string | null) {
if (!dateStr)
return '-'
return new Date(dateStr).toLocaleDateString('zh-CN')
+8 -1
View File
@@ -18,7 +18,14 @@ const recentCards = ref<any[]>([])
onMounted(async () => {
try {
const data = await api.get('/agent/stats')
const data = await api.get<{
totalApps?: number
totalCards?: number
totalUsers?: number
totalRevenue?: number
todayCards?: number
todayRevenue?: number
}>('/agent/stats')
if (data) {
stats.value = {
totalApps: data.totalApps || 0,
+1 -1
View File
@@ -29,7 +29,7 @@ onMounted(async () => {
}
})
function formatDate(dateStr: string) {
function formatDate(dateStr: string | null) {
if (!dateStr)
return '-'
return new Date(dateStr).toLocaleDateString('zh-CN')
@@ -77,7 +77,7 @@ defineExpose({
type="search"
:placeholder="t('developer.agents.searchPlaceholder')"
class="h-9 w-[250px] pl-8"
@update:model-value="emit('update:searchFilter', $event)"
@update:model-value="emit('update:searchFilter', String($event))"
/>
</div>
<slot name="filters" />
@@ -18,7 +18,7 @@ const router = useRouter()
const loading = ref(true)
const agents = ref<Agent[]>([])
const tableRef = ref()
const _tableRef = ref<InstanceType<typeof DataTable> | null>(null)
const searchFilter = ref('')
const statusFilter = ref<string>('')
const viewMode = ref<'tree' | 'list'>('tree')
@@ -10,12 +10,12 @@ import api from '@/services/api'
interface Variable {
id: number
key: string
data_type: string
var_type: string
scope: string
max_records: number
}
interface Record {
interface VariableRecord {
id: number
data: Record<string, any>
user_id?: number
@@ -31,7 +31,7 @@ const router = useRouter()
const loading = ref(true)
const variable = ref<Variable | null>(null)
const records = ref<Record[]>([])
const records = ref<VariableRecord[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
@@ -44,8 +44,8 @@ async function fetchVariable() {
const data = await api.get<{ variables: Variable[] }>(`/dev/cloud-variables?app_id=${route.query.app_id}`)
const vars = data?.variables || []
variable.value = vars.find((v: Variable) => String(v.id) === route.params.id) || null
if (variable.value && variable.value.data_type !== 'stream') {
toast.error('该变量不是流水类型')
if (variable.value && variable.value.var_type !== 'stream') {
toast.error('该变量不是记录类型')
router.back()
}
}
@@ -68,7 +68,7 @@ async function fetchRecords() {
if (endDate.value)
params.append('end_date', endDate.value)
const data = await api.get(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
const data = await api.get<{ records: VariableRecord[], total: number }>(`/dev/cloud-variables/${variable.value.id}/records?${params}`)
records.value = data?.records || []
total.value = data?.total || 0
}
@@ -141,11 +141,11 @@ onMounted(async () => {
<template>
<BasicPage
title="流水记录"
description="查看云端变量的流水记录数据"
title="变量记录"
description="查看云端变量的记录数据"
:breadcrumbs="[
{ title: '云端变量', href: '/developer/cloud-variables' },
{ title: '流水记录' },
{ title: '变量记录' },
]"
>
<div class="space-y-6">
@@ -3,7 +3,6 @@ import type { Composer } from 'vue-i18n'
import { Download, File, List, MoreHorizontal, Pencil, Power, PowerOff, Trash2 } from 'lucide-vue-next'
import { h } from 'vue'
import { useRouter } from 'vue-router'
import type { CloudVariable } from '@/pages/developer/cloud-variables/data/schema'
@@ -88,21 +87,11 @@ export function getColumns(actions: {
decimal: t('developer.cloudVariables.types.decimal'),
string: t('developer.cloudVariables.types.string'),
binary: t('developer.cloudVariables.types.binary'),
stream: '记录',
}
return h(Badge, { variant: 'outline' }, () => typeMap[type || 'string'] || type || 'string')
},
},
{
accessorKey: 'data_type',
header: () => '数据模式',
cell: ({ row }) => {
const dataType = row.getValue('data_type') as string
if (dataType === 'stream') {
return h(Badge, { variant: 'secondary', class: 'bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300' }, () => '流水')
}
return h(Badge, { variant: 'outline' }, () => '单值')
},
},
{
accessorKey: 'description',
header: () => t('developer.cloudVariables.columns.description'),
@@ -182,7 +171,7 @@ export function getColumns(actions: {
() => {
const items = []
if (variable.data_type === 'stream') {
if (variable.var_type === 'stream') {
items.push(
h(DropdownMenuItem, { onClick: () => actions.onViewRecords(variable) }, () => [
h(List, { class: 'mr-2 h-4 w-4' }),
@@ -36,7 +36,7 @@ function viewRecords() {
</UiButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent align="end" class="w-[160px]">
<UiDropdownMenuItem v-if="variable.data_type === 'stream'" @click="viewRecords">
<UiDropdownMenuItem v-if="variable.var_type === 'stream'" @click="viewRecords">
<List class="mr-2 h-4 w-4" />
查看记录
</UiDropdownMenuItem>
@@ -27,7 +27,6 @@ const form = ref({
key: '',
default_value: '',
var_type: 'string',
data_type: 'single',
max_records: 0,
scope: 'app',
write_permission: 'admin',
@@ -95,7 +94,6 @@ async function handleSave() {
key: form.value.key,
default_value: form.value.default_value,
var_type: form.value.var_type,
data_type: form.value.data_type,
max_records: form.value.max_records,
scope: form.value.scope,
write_permission: form.value.write_permission,
@@ -215,47 +213,14 @@ onMounted(() => {
<UiSelectItem value="binary">
二进制
</UiSelectItem>
<UiSelectItem value="stream">
记录
</UiSelectItem>
</UiSelectContent>
</UiSelect>
</div>
<div class="space-y-2">
<UiLabel>数据模式</UiLabel>
<UiRadioGroup v-model="form.data_type" class="grid grid-cols-2 gap-4">
<div
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
:class="form.data_type === 'single' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.data_type = 'single'"
>
<UiRadioGroupItem value="single" class="mt-0.5" />
<div>
<p class="font-medium text-sm">
单值模式
</p>
<p class="text-xs text-muted-foreground mt-1">
存储单个值适合配置项
</p>
</div>
</div>
<div
class="flex items-start gap-3 p-3 rounded-lg border cursor-pointer transition-colors"
:class="form.data_type === 'stream' ? 'border-primary bg-primary/5' : 'hover:bg-accent'"
@click="form.data_type = 'stream'"
>
<UiRadioGroupItem value="stream" class="mt-0.5" />
<div>
<p class="font-medium text-sm">
流水模式
</p>
<p class="text-xs text-muted-foreground mt-1">
存储记录列表适合订单日志等
</p>
</div>
</div>
</UiRadioGroup>
</div>
<div v-if="form.data_type === 'stream'" class="space-y-2">
<div v-if="form.var_type === 'stream'" class="space-y-2">
<UiLabel for="maxRecords">
最大记录数
</UiLabel>
@@ -8,7 +8,6 @@ export const cloudVariableSchema = z.object({
key: z.string(),
default_value: z.string().optional().nullable(),
var_type: z.string().optional().nullable(),
data_type: z.string().optional().nullable(),
max_records: z.union([z.string(), z.number()]).optional().nullable(),
file_path: z.string().optional().nullable(),
file_size: z.union([z.string(), z.number()]).optional().nullable(),
@@ -268,9 +268,7 @@ onMounted(() => {
<div class="text-muted-foreground mb-2">
{{ t('developer.cloudFunction.create.code') }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
{{ form.code || '-' }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre !important;">{{ form.code || '-' }}</div>
</div>
</div>
</div>
@@ -221,9 +221,7 @@ onMounted(() => {
<div class="text-muted-foreground mb-2">
{{ t('developer.cloudFunction.create.code') }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-y-auto">
{{ form.code || '-' }}
</div>
<div class="font-mono text-xs bg-muted p-2 rounded max-h-[150px] overflow-auto" style="white-space: pre !important;">{{ form.code || '-' }}</div>
</div>
</div>
</div>
+4 -2
View File
@@ -1152,7 +1152,8 @@
"integer": "Integer",
"decimal": "Decimal",
"string": "String",
"binary": "Binary"
"binary": "Binary",
"stream": "Record"
},
"create": {
"config": "Configuration",
@@ -1238,7 +1239,8 @@
"integer": "Integer",
"decimal": "Decimal",
"string": "String",
"binary": "Binary"
"binary": "Binary",
"stream": "Record"
},
"create": {
"config": "Configuration",
+4 -2
View File
@@ -1128,7 +1128,8 @@
"integer": "整数",
"decimal": "小数",
"string": "字符串",
"binary": "二进制"
"binary": "二进制",
"stream": "记录"
},
"create": {
"config": "配置",
@@ -1214,7 +1215,8 @@
"integer": "整数",
"decimal": "小数",
"string": "字符串",
"binary": "二进制"
"binary": "二进制",
"stream": "记录"
},
"create": {
"config": "配置",
-16
View File
@@ -12,8 +12,6 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AdminSidebar: typeof import('./../components/admin-sidebar/index.vue')['default']
AdminSidebarNavFooter: typeof import('./../components/admin-sidebar/nav-footer.vue')['default']
AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
@@ -57,14 +55,8 @@ declare module 'vue' {
InspiraUiRippleContainer: typeof import('./../components/inspira-ui/ripple/container.vue')['default']
LanguageChange: typeof import('./../components/language-change.vue')['default']
Loading: typeof import('./../components/loading.vue')['default']
MarketingEvaluation: typeof import('./../components/marketing/evaluation.vue')['default']
MarketingFeatures: typeof import('./../components/marketing/features.vue')['default']
MarketingHero: typeof import('./../components/marketing/hero.vue')['default']
MarketingLayoutTheFooter: typeof import('./../components/marketing-layout/the-footer.vue')['default']
MarketingLayoutTheHeader: typeof import('./../components/marketing-layout/the-header.vue')['default']
MarketingLogos: typeof import('./../components/marketing/logos.vue')['default']
MarketingPricingPlans: typeof import('./../components/marketing/pricing-plans/index.vue')['default']
MarketingSetup: typeof import('./../components/marketing/setup.vue')['default']
NoResultFound: typeof import('./../components/no-result-found.vue')['default']
PropUiModal: typeof import('./../components/prop-ui/modal/Modal.vue')['default']
PropUiModalClose: typeof import('./../components/prop-ui/modal/ModalClose.vue')['default']
@@ -423,8 +415,6 @@ declare module 'vue' {
// For TSX support
declare global {
const AdminSidebar: typeof import('./../components/admin-sidebar/index.vue')['default']
const AdminSidebarNavFooter: typeof import('./../components/admin-sidebar/nav-footer.vue')['default']
const AppSidebar: typeof import('./../components/app-sidebar/index.vue')['default']
const AppSidebarNavFooter: typeof import('./../components/app-sidebar/nav-footer.vue')['default']
const AppSidebarNavTeam: typeof import('./../components/app-sidebar/nav-team.vue')['default']
@@ -468,14 +458,8 @@ declare global {
const InspiraUiRippleContainer: typeof import('./../components/inspira-ui/ripple/container.vue')['default']
const LanguageChange: typeof import('./../components/language-change.vue')['default']
const Loading: typeof import('./../components/loading.vue')['default']
const MarketingEvaluation: typeof import('./../components/marketing/evaluation.vue')['default']
const MarketingFeatures: typeof import('./../components/marketing/features.vue')['default']
const MarketingHero: typeof import('./../components/marketing/hero.vue')['default']
const MarketingLayoutTheFooter: typeof import('./../components/marketing-layout/the-footer.vue')['default']
const MarketingLayoutTheHeader: typeof import('./../components/marketing-layout/the-header.vue')['default']
const MarketingLogos: typeof import('./../components/marketing/logos.vue')['default']
const MarketingPricingPlans: typeof import('./../components/marketing/pricing-plans/index.vue')['default']
const MarketingSetup: typeof import('./../components/marketing/setup.vue')['default']
const NoResultFound: typeof import('./../components/no-result-found.vue')['default']
const PropUiModal: typeof import('./../components/prop-ui/modal/Modal.vue')['default']
const PropUiModalClose: typeof import('./../components/prop-ui/modal/ModalClose.vue')['default']
+3
View File
@@ -14,6 +14,9 @@ import type {
ParamValueZeroOrMore,
ParamValueZeroOrOne,
} from 'vue-router'
import type {
_ExtractParamParserType,
} from 'vue-router/experimental'
declare module 'vue-router' {
interface TypesConfig {