2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 22:07:16 +08:00
Files
panel/web/src/views/project/CreateModal.vue
2026-01-11 01:17:52 +08:00

224 lines
6.3 KiB
Vue

<script setup lang="ts">
import { useGettext } from 'vue3-gettext'
import home from '@/api/panel/home'
import project from '@/api/panel/project'
import PathSelector from '@/components/common/PathSelector.vue'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const type = defineModel<string>('type', { type: String, required: true })
const { $gettext } = useGettext()
// PHP 框架预设
const phpFrameworks = [
{ label: $gettext('Custom'), value: 'custom', command: '' },
{ label: 'Laravel Octane', value: 'laravel-octane', command: 'artisan octane:start' },
{ label: 'Laravel (Artisan Serve)', value: 'laravel-serve', command: 'artisan serve' },
{ label: 'ThinkPHP', value: 'thinkphp', command: 'think run' },
{ label: 'Webman', value: 'webman', command: 'start.php start' },
{ label: 'Hyperf', value: 'hyperf', command: 'bin/hyperf.php start' },
{ label: 'Swoole HTTP', value: 'swoole', command: 'server.php' },
{ label: 'RoadRunner', value: 'roadrunner', command: 'vendor/bin/rr serve' }
]
const createModel = ref({
name: '',
type: '',
root_dir: '/opt/ace',
working_dir: '',
exec_start: '',
user: 'www'
})
// PHP 特有字段
const phpOptions = ref({
version: null as number | null,
framework: 'custom'
})
const showPathSelector = ref(false)
const pathSelectorPath = ref('/opt/ace')
const { data: installedEnvironment } = useRequest(home.installedEnvironment, {
initialData: {
php: []
}
})
// PHP 版本选项
const phpVersionOptions = computed(() => {
return installedEnvironment.value?.php || []
})
// 根据 PHP 版本和框架生成启动命令
const generateCommand = () => {
if (type.value !== 'php' || !phpOptions.value.version) {
return
}
const framework = phpFrameworks.find((f) => f.value === phpOptions.value.framework)
if (!framework || framework.value === 'custom') {
return
}
const phpBin = `php${phpOptions.value.version}`
createModel.value.exec_start = `${phpBin} ${framework.command}`
}
// 监听 PHP 版本和框架变化
watch(
() => [phpOptions.value.version, phpOptions.value.framework],
() => {
generateCommand()
}
)
// 处理目录选择
const handleSelectPath = () => {
pathSelectorPath.value = createModel.value.root_dir || '/opt/ace'
showPathSelector.value = true
}
// 目录选择完成
watch(showPathSelector, (val) => {
if (!val && pathSelectorPath.value) {
createModel.value.root_dir = pathSelectorPath.value
}
})
const handleCreate = async () => {
createModel.value.type = type.value == 'all' ? 'general' : type.value
useRequest(project.create(createModel.value)).onSuccess(() => {
window.$bus.emit('project:refresh')
window.$message.success($gettext('Project created successfully'))
show.value = false
// 重置表单
createModel.value = {
name: '',
type: '',
root_dir: '/opt/ace',
working_dir: '',
exec_start: '',
user: 'www'
}
phpOptions.value = {
version: null,
framework: 'custom'
}
})
}
// 根据类型获取标题
const modalTitle = computed(() => {
const titles: Record<string, string> = {
general: $gettext('Create General Project'),
php: $gettext('Create PHP Project')
}
return titles[type.value] || $gettext('Create Project')
})
</script>
<template>
<n-modal
v-model:show="show"
:title="modalTitle"
preset="card"
style="width: 60vw"
size="huge"
:bordered="false"
:segmented="false"
@close="show = false"
>
<n-form :model="createModel" label-placement="left" label-width="100">
<n-form-item path="name" :label="$gettext('Project Name')">
<n-input
v-model:value="createModel.name"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Project name, used as service identifier')"
/>
</n-form-item>
<n-form-item path="root_dir" :label="$gettext('Project Directory')" required>
<n-input-group>
<n-input
v-model:value="createModel.root_dir"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('Project root directory')"
/>
<n-button @click="handleSelectPath">
<template #icon>
<i-mdi-folder-open />
</template>
</n-button>
</n-input-group>
</n-form-item>
<!-- PHP 类型特有字段 -->
<template v-if="type === 'php'">
<n-row :gutter="[24, 0]">
<n-col :span="12">
<n-form-item :label="$gettext('PHP Version')">
<n-select
v-model:value="phpOptions.version"
:options="phpVersionOptions"
:placeholder="$gettext('Select PHP Version')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
<n-col :span="12">
<n-form-item :label="$gettext('Framework')">
<n-select
v-model:value="phpOptions.framework"
:options="phpFrameworks"
:placeholder="$gettext('Select Framework')"
@keydown.enter.prevent
/>
</n-form-item>
</n-col>
</n-row>
</template>
<n-form-item path="user" :label="$gettext('Run User')">
<n-select
v-model:value="createModel.user"
:options="[
{ label: 'www', value: 'www' },
{ label: 'root', value: 'root' },
{ label: 'nobody', value: 'nobody' }
]"
:placeholder="$gettext('Select User')"
@keydown.enter.prevent
/>
<template #feedback>
<span class="text-gray-400">
{{ $gettext('Select www user if no special requirements') }}
</span>
</template>
</n-form-item>
<n-form-item path="exec_start" :label="$gettext('Start Command')" required>
<n-input
v-model:value="createModel.exec_start"
type="text"
@keydown.enter.prevent
:placeholder="$gettext('e.g., php artisan serve, node app.js')"
/>
</n-form-item>
</n-form>
<n-button type="info" block @click="handleCreate">
{{ $gettext('Create') }}
</n-button>
</n-modal>
<!-- 目录选择器 -->
<path-selector v-model:show="showPathSelector" v-model:path="pathSelectorPath" :dir="true" />
</template>
<style scoped lang="scss"></style>