337 lines
8.3 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="140px"
size="large"
>
<n-grid :cols="3" :x-gap="24" responsive="screen">
<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>
</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
}
}
</script>
<style scoped>
.create-app-template {
padding: 16px 24px;
width: 100%;
margin: 0;
min-height: 100vh;
}
.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;
width: 100%;
}
.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;
}
/* 响应式布局 */
@media (max-width: 1200px) {
.create-app-template {
padding: 12px 16px;
}
}
@media (max-width: 768px) {
.create-app-template {
padding: 8px 12px;
}
.page-header {
margin-bottom: 16px;
}
.content {
margin-top: 16px;
}
}
</style>