2025-10-24 23:10:22 +08:00

364 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="create-app-template">
<div class="page-header">
<div class="header-content">
<div class="header-text">
<h1>{{ t('Create App Template') }}</h1>
<p class="description">{{ t('Create a modern, efficient template app') }}</p>
</div>
</div>
</div>
<div class="content">
<n-card>
<n-form
ref="formRef"
:model="form"
:rules="rules"
label-placement="left"
label-width="120px"
size="large"
>
<n-grid :cols="2" :x-gap="24">
<n-form-item-grid-item>
<n-form-item :label="t('App Name')" path="appName">
<n-input
v-model:value="form.appName"
:placeholder="t('Enter app name (spaces auto-converted to underscores)')"
@input="updateSlug"
/>
</n-form-item>
</n-form-item-grid-item>
<n-form-item-grid-item>
<n-form-item :label="t('App Title')" path="appTitle">
<n-input
v-model:value="form.appTitle"
:placeholder="t('App title (auto-generated from app name)')"
/>
</n-form-item>
</n-form-item-grid-item>
<n-form-item-grid-item>
<n-form-item :label="t('Publisher')" path="publisher">
<n-input
v-model:value="form.publisher"
:placeholder="t('Enter publisher name')"
/>
</n-form-item>
</n-form-item-grid-item>
<n-form-item-grid-item>
<n-form-item :label="t('Description')" path="description">
<n-input
v-model:value="form.description"
:placeholder="t('Enter app description')"
/>
</n-form-item>
</n-form-item-grid-item>
<n-form-item-grid-item>
<n-form-item :label="t('Email')" path="email">
<n-input
v-model:value="form.email"
:placeholder="t('Enter support email')"
/>
</n-form-item>
</n-form-item-grid-item>
<n-form-item-grid-item>
<n-form-item :label="t('License')" path="license">
<n-select
v-model:value="form.license"
:options="licenseOptions"
:placeholder="t('Select license')"
/>
</n-form-item>
</n-form-item-grid-item>
</n-grid>
<div class="form-actions">
<n-space>
<n-button @click="resetForm" size="large">
{{ t('Reset') }}
</n-button>
<n-button
type="primary"
size="large"
:loading="submitting"
@click="createAppTemplate"
>
<template #icon>
<n-icon><Icon icon="tabler:app-window" /></n-icon>
</template>
{{ t('Create App Template') }}
</n-button>
</n-space>
</div>
</n-form>
</n-card>
<!-- 创建结果 -->
<n-card v-if="result" class="mt-6">
<template #header>
<div class="flex items-center gap-2">
<n-icon color="#18a058"><Icon icon="tabler:check" /></n-icon>
<span>{{ t('App Template Created Successfully') }}</span>
</div>
</template>
<n-descriptions :column="2" bordered>
<n-descriptions-item :label="t('App Name')">
{{ result.appName }}
</n-descriptions-item>
<n-descriptions-item :label="t('App Path')">
<n-text code>{{ result.appPath }}</n-text>
</n-descriptions-item>
<n-descriptions-item :label="t('Backend Path')">
<n-text code>{{ result.backendPath }}</n-text>
</n-descriptions-item>
<n-descriptions-item :label="t('Frontend Path')">
<n-text code>{{ result.frontendPath }}</n-text>
</n-descriptions-item>
</n-descriptions>
<div class="mt-4">
<n-alert type="success" :show-icon="false">
<template #header>
{{ t('Next Steps') }}
</template>
<ol class="list-decimal list-inside space-y-2">
<li>{{ t('Navigate to the app directory') }}: <n-text code>{{ result.appPath }}</n-text></li>
<li>{{ t('Install dependencies') }}: <n-text code>pip install -r requirements.txt</n-text></li>
<li>{{ t('Test locally') }}: <n-text code>python -m uvicorn main:app --port 8001</n-text></li>
<li>{{ t('Deploy to Jingrow Cloud') }}: <n-text code>bench --site your-site install-app {{ result.appName }}</n-text></li>
</ol>
</n-alert>
</div>
<div class="mt-4 flex gap-2">
<n-button @click="openInExplorer" type="primary">
<template #icon>
<n-icon><Icon icon="tabler:folder-open" /></n-icon>
</template>
{{ t('Open in Explorer') }}
</n-button>
<n-button @click="copyToClipboard" type="default">
<template #icon>
<n-icon><Icon icon="tabler:copy" /></n-icon>
</template>
{{ t('Copy Path') }}
</n-button>
</div>
</n-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useMessage } from 'naive-ui'
import { Icon } from '@iconify/vue'
import axios from 'axios'
import { t } from '@/shared/i18n'
const message = useMessage()
const formRef = ref()
const submitting = ref(false)
const result = ref<any>(null)
const form = ref({
appName: '',
appTitle: '',
publisher: 'Your Company',
description: '',
email: 'support@yourcompany.com',
license: 'MIT'
})
const licenseOptions = [
{ label: 'MIT', value: 'MIT' },
{ label: 'Apache 2.0', value: 'Apache 2.0' },
{ label: 'GPL v3', value: 'GPL v3' },
{ label: 'AGPL v3', value: 'AGPL v3' },
{ label: 'BSD 3-Clause', value: 'BSD 3-Clause' }
]
const rules = {
appName: [
{ required: true, message: t('App name is required') },
{ pattern: /^[a-z][a-z0-9_]*$/, message: t('App name must start with lowercase letter and contain only lowercase letters, numbers, and underscores') }
],
appTitle: [
{ required: true, message: t('App title is required') }
],
publisher: [
{ required: true, message: t('Publisher is required') }
],
description: [
{ required: true, message: t('Description is required') }
],
email: [
{ required: true, message: t('Email is required') },
{ type: 'email', message: t('Please enter a valid email') }
]
}
const updateSlug = () => {
if (form.value.appName) {
// 自动转换为小写,空格转换为下划线
form.value.appName = form.value.appName
.toLowerCase()
.replace(/\s+/g, '_')
.replace(/[^a-z0-9_]/g, '') // 只保留小写字母、数字和下划线
// 自动生成应用标题:将下划线分隔的单词转换为标题格式
// 例如my_app -> My App, customer_management -> Customer Management
form.value.appTitle = form.value.appName
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ')
} else {
// 如果应用名称为空,清空应用标题
form.value.appTitle = ''
}
}
const resetForm = () => {
form.value = {
appName: '',
appTitle: '',
publisher: 'Your Company',
description: '',
email: 'support@yourcompany.com',
license: 'MIT'
}
result.value = null
}
const createAppTemplate = async () => {
try {
await formRef.value?.validate()
submitting.value = true
const response = await axios.post('/jingrow/dev/create-app-template', form.value)
if (response.data.success) {
result.value = response.data
message.success(t('App template created successfully'))
} else {
message.error(response.data.error || t('Failed to create app template'))
}
} catch (error: any) {
console.error('Create app template error:', error)
message.error(error.response?.data?.detail || error.message || t('Failed to create app template'))
} finally {
submitting.value = false
}
}
const openInExplorer = () => {
if (result.value?.appPath) {
// 在开发环境中,尝试打开文件管理器
if (import.meta.env.DEV) {
window.open(`file://${result.value.appPath}`)
} else {
message.info(t('Please navigate to the path manually'))
}
}
}
const copyToClipboard = async () => {
if (result.value?.appPath) {
try {
await navigator.clipboard.writeText(result.value.appPath)
message.success(t('Path copied to clipboard'))
} catch (error) {
message.error(t('Failed to copy to clipboard'))
}
}
}
</script>
<style scoped>
.create-app-template {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
margin-bottom: 24px;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
}
.header-text h1 {
font-size: 24px;
font-weight: 600;
margin: 0 0 8px 0;
color: var(--text-color);
}
.description {
color: var(--text-color-2);
margin: 0;
}
.content {
margin-top: 24px;
}
.form-actions {
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
}
.ml-2 {
margin-left: 8px;
}
.mt-4 {
margin-top: 16px;
}
.mt-6 {
margin-top: 24px;
}
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.gap-2 {
gap: 8px;
}
.gap-24 {
gap: 24px;
}
.space-y-2 > * + * {
margin-top: 8px;
}
.list-decimal {
list-style-type: decimal;
}
.list-inside {
list-style-position: inside;
}
</style>