mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 17:17:13 +08:00
feat: 网站重构2
This commit is contained in:
@@ -295,9 +295,26 @@ func (r *websiteRepo) Create(req *request.WebsiteCreate) (*biz.Website, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建配置文件目录
|
||||
if err = os.MkdirAll(filepath.Join(app.Root, "sites", req.Name, "config", "vhost"), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Join(app.Root, "sites", req.Name, "config", "global"), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 创建日志目录
|
||||
if err = os.MkdirAll(filepath.Join(app.Root, "sites", req.Name, "log"), 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 反向代理支持
|
||||
if proxyVhost, ok := vhost.(webservertypes.ProxyVhost); ok {
|
||||
if err = proxyVhost.SetProxies([]webservertypes.Proxy{req.Proxy}); err != nil {
|
||||
if err = proxyVhost.SetProxies([]webservertypes.Proxy{
|
||||
{
|
||||
Location: "^~ /",
|
||||
Pass: req.Proxy,
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package request
|
||||
|
||||
import (
|
||||
"github.com/acepanel/panel/pkg/types"
|
||||
webservertypes "github.com/acepanel/panel/pkg/webserver/types"
|
||||
)
|
||||
|
||||
type WebsiteDefaultConfig struct {
|
||||
@@ -28,8 +27,8 @@ type WebsiteCreate struct {
|
||||
DBPassword string `form:"db_password" json:"db_password" validate:"requiredIf:DB,true"`
|
||||
Remark string `form:"remark" json:"remark"`
|
||||
|
||||
PHP uint `form:"php" json:"php" validate:"requiredIf:Type,php"` // 仅 PHP 网站需要
|
||||
Proxy webservertypes.Proxy `form:"proxy" json:"proxy" validate:"requiredIf:Type,proxy"` // 仅反向代理网站需要
|
||||
PHP uint `form:"php" json:"php" validate:"requiredIf:Type,php"` // 仅 PHP 网站需要
|
||||
Proxy string `form:"proxy" json:"proxy" validate:"requiredIf:Type,proxy"` // 仅反向代理网站需要
|
||||
}
|
||||
|
||||
type WebsiteDelete struct {
|
||||
|
||||
@@ -112,7 +112,7 @@ func (s *WebsiteService) Create(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(req.Path) == 0 {
|
||||
req.Path, _ = s.settingRepo.Get(biz.SettingKeyWebsitePath)
|
||||
req.Path = filepath.Join(req.Path, req.Name)
|
||||
req.Path = filepath.Join(req.Path, req.Name, "public")
|
||||
}
|
||||
|
||||
if _, err = s.websiteRepo.Create(req); err != nil {
|
||||
|
||||
@@ -254,7 +254,7 @@ func (v *baseVhost) Save() error {
|
||||
}
|
||||
|
||||
func (v *baseVhost) Reload() error {
|
||||
parts := strings.Fields("systemctl reload openresty")
|
||||
parts := strings.Fields("systemctl reload nginx")
|
||||
if err := exec.Command(parts[0], parts[1:]...).Run(); err != nil {
|
||||
if testErr := exec.Command("nginx", "-t").Run(); testErr != nil {
|
||||
return fmt.Errorf("nginx config test failed: %w", testErr)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import website from '@/api/panel/website'
|
||||
import { NButton } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
import website from '@/api/panel/website'
|
||||
|
||||
const show = defineModel<boolean>('show', { type: Boolean, required: true })
|
||||
const type = defineModel<string>('type', { type: String, required: true })
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const bulkCreate = ref('')
|
||||
|
||||
245
web/src/views/website/CreateModal.vue
Normal file
245
web/src/views/website/CreateModal.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NInput } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import home from '@/api/panel/home'
|
||||
import website from '@/api/panel/website'
|
||||
import { generateRandomString } from '@/utils'
|
||||
|
||||
const show = defineModel<boolean>('show', { type: Boolean, required: true })
|
||||
const type = defineModel<string>('type', { type: String, required: true })
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const createModel = ref({
|
||||
type: '',
|
||||
name: '',
|
||||
listens: [] as Array<string>,
|
||||
domains: [] as Array<string>,
|
||||
path: '',
|
||||
db: false,
|
||||
db_type: '0',
|
||||
db_name: '',
|
||||
db_user: '',
|
||||
db_password: '',
|
||||
remark: '',
|
||||
|
||||
php: 0,
|
||||
proxy: ''
|
||||
})
|
||||
|
||||
const { data: installedDbAndPhp } = useRequest(home.installedDbAndPhp, {
|
||||
initialData: {
|
||||
php: [
|
||||
{
|
||||
label: $gettext('Not used'),
|
||||
value: 0
|
||||
}
|
||||
],
|
||||
db: [
|
||||
{
|
||||
label: '',
|
||||
value: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const handleCreate = async () => {
|
||||
createModel.value.type = type.value
|
||||
// 去除空的域名和端口
|
||||
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
|
||||
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
|
||||
// 端口为空自动添加 80 端口
|
||||
if (createModel.value.listens.length === 0) {
|
||||
createModel.value.listens.push('80')
|
||||
}
|
||||
// 端口中去掉 443 端口,不允许在未配置证书下监听 443 端口
|
||||
createModel.value.listens = createModel.value.listens.filter((item) => item !== '443')
|
||||
useRequest(website.create(createModel.value)).onSuccess(() => {
|
||||
window.$bus.emit('website:refresh')
|
||||
window.$message.success(
|
||||
$gettext('Website %{ name } created successfully', { name: createModel.value.name })
|
||||
)
|
||||
show.value = false
|
||||
createModel.value = {
|
||||
type: '',
|
||||
name: '',
|
||||
domains: [] as Array<string>,
|
||||
listens: [] as Array<string>,
|
||||
db: false,
|
||||
db_type: '0',
|
||||
db_name: '',
|
||||
db_user: '',
|
||||
db_password: '',
|
||||
path: '',
|
||||
remark: '',
|
||||
php: 0,
|
||||
proxy: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formatDbValue = (value: string) => {
|
||||
value = value.replace(/\./g, '_')
|
||||
value = value.replace(/-/g, '_')
|
||||
if (value.length > 16) {
|
||||
value = value.substring(0, 16)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="show"
|
||||
:title="$gettext('Create Website')"
|
||||
preset="card"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="show = false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="name" :label="$gettext('Name')">
|
||||
<n-input
|
||||
v-model:value="createModel.name"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="
|
||||
$gettext('Must use English for the website name, it cannot be modified after setting')
|
||||
"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="11">
|
||||
<n-form-item :label="$gettext('Domain')">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.domains"
|
||||
placeholder="example.com"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="2"></n-col>
|
||||
<n-col :span="11">
|
||||
<n-form-item :label="$gettext('Port')">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.listens"
|
||||
placeholder="80"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-row v-if="type == 'php'" :gutter="[0, 24]">
|
||||
<n-col :span="11">
|
||||
<n-form-item path="php" :label="$gettext('PHP Version')">
|
||||
<n-select
|
||||
v-model:value="createModel.php"
|
||||
:options="installedDbAndPhp.php"
|
||||
:placeholder="$gettext('Select PHP Version')"
|
||||
@keydown.enter.prevent
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="2"></n-col>
|
||||
<n-col :span="11">
|
||||
<n-form-item path="db" :label="$gettext('Database')">
|
||||
<n-select
|
||||
v-model:value="createModel.db_type"
|
||||
:options="installedDbAndPhp.db"
|
||||
:placeholder="$gettext('Select Database')"
|
||||
@keydown.enter.prevent
|
||||
@update:value="
|
||||
() => {
|
||||
createModel.db = createModel.db_type != '0'
|
||||
createModel.db_name = formatDbValue(createModel.name)
|
||||
createModel.db_user = formatDbValue(createModel.name)
|
||||
createModel.db_password = generateRandomString(16)
|
||||
}
|
||||
"
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-row v-if="type == 'php'" :gutter="[0, 24]">
|
||||
<n-col :span="7">
|
||||
<n-form-item v-if="createModel.db" path="db_name" :label="$gettext('Database Name')">
|
||||
<n-input
|
||||
v-model:value="createModel.db_name"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Database Name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="1"></n-col>
|
||||
<n-col :span="7">
|
||||
<n-form-item v-if="createModel.db" path="db_user" :label="$gettext('Database User')">
|
||||
<n-input
|
||||
v-model:value="createModel.db_user"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Database User')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="1"></n-col>
|
||||
<n-col :span="8">
|
||||
<n-form-item
|
||||
v-if="createModel.db"
|
||||
path="db_password"
|
||||
:label="$gettext('Database Password')"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="createModel.db_password"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Database Password')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-form-item v-if="type != 'proxy'" path="path" :label="$gettext('Directory')">
|
||||
<n-input
|
||||
v-model:value="createModel.path"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="
|
||||
$gettext(
|
||||
'Website root directory (if left empty, defaults to website directory/website name/public)'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="type == 'proxy'" path="path" :label="$gettext('Proxy Target')">
|
||||
<n-input
|
||||
v-model:value="createModel.proxy"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Proxy target address (e.g., http://127.0.0.1:3000)')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$gettext('Remark')">
|
||||
<n-input
|
||||
v-model:value="createModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Remark')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleCreate">
|
||||
{{ $gettext('Create') }}
|
||||
</n-button>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -3,10 +3,15 @@ defineOptions({
|
||||
name: 'website-index'
|
||||
})
|
||||
|
||||
import BulkCreateModal from '@/views/website/BulkCreateModal.vue'
|
||||
import CreateModal from '@/views/website/CreateModal.vue'
|
||||
import ListView from '@/views/website/ListView.vue'
|
||||
import SettingView from '@/views/website/SettingView.vue'
|
||||
|
||||
const currentTab = ref('proxy')
|
||||
|
||||
const createModal = ref(false)
|
||||
const bulkCreateModal = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,7 +24,15 @@ const currentTab = ref('proxy')
|
||||
<n-tab name="setting" :tab="$gettext('Settings')" />
|
||||
</n-tabs>
|
||||
</template>
|
||||
<list-view v-if="currentTab != 'setting'" v-model:type="currentTab" />
|
||||
<list-view
|
||||
v-if="currentTab != 'setting'"
|
||||
v-model:type="currentTab"
|
||||
v-model:create-modal="createModal"
|
||||
v-model:bulk-create-modal="bulkCreateModal"
|
||||
/>
|
||||
<setting-view v-if="currentTab === 'setting'" />
|
||||
|
||||
<create-modal v-model:show="createModal" v-model:type="currentTab" />
|
||||
<bulk-create-modal v-model:show="bulkCreateModal" v-model:type="currentTab" />
|
||||
</common-page>
|
||||
</template>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
import { NButton, NCheckbox, NDataTable, NFlex, NInput, NPopconfirm, NSwitch, NTag } from 'naive-ui'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
|
||||
import home from '@/api/panel/home'
|
||||
import website from '@/api/panel/website'
|
||||
import { useFileStore } from '@/store'
|
||||
import { generateRandomString, isNullOrUndef } from '@/utils'
|
||||
import BulkCreate from '@/views/website/BulkCreate.vue'
|
||||
import { isNullOrUndef } from '@/utils'
|
||||
|
||||
const type = defineModel<string>('type', { type: String, required: true }) // 网站类型
|
||||
const createModal = defineModel<boolean>('createModal', { type: Boolean, required: true }) // 创建网站
|
||||
const bulkCreateModal = defineModel<boolean>('bulkCreateModal', { type: Boolean, required: true }) // 批量创建网站
|
||||
|
||||
const fileStore = useFileStore()
|
||||
const { $gettext } = useGettext()
|
||||
@@ -203,44 +203,11 @@ const columns: any = [
|
||||
}
|
||||
]
|
||||
|
||||
const createModal = ref(false)
|
||||
const bulkCreateModal = ref(false)
|
||||
|
||||
const createModel = ref({
|
||||
name: '',
|
||||
listens: [] as Array<string>,
|
||||
domains: [] as Array<string>,
|
||||
path: '',
|
||||
php: 0,
|
||||
db: false,
|
||||
db_type: '0',
|
||||
db_name: '',
|
||||
db_user: '',
|
||||
db_password: '',
|
||||
remark: ''
|
||||
})
|
||||
const deleteModel = ref({
|
||||
path: true,
|
||||
db: false
|
||||
})
|
||||
|
||||
const { data: installedDbAndPhp } = useRequest(home.installedDbAndPhp, {
|
||||
initialData: {
|
||||
php: [
|
||||
{
|
||||
label: $gettext('Not used'),
|
||||
value: 0
|
||||
}
|
||||
],
|
||||
db: [
|
||||
{
|
||||
label: '',
|
||||
value: ''
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const { loading, data, page, total, pageSize, pageCount, refresh } = usePagination(
|
||||
(page, pageSize) => website.list(type.value, page, pageSize),
|
||||
{
|
||||
@@ -288,38 +255,6 @@ const handleDelete = (id: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
// 去除空的域名和端口
|
||||
createModel.value.domains = createModel.value.domains.filter((item) => item !== '')
|
||||
createModel.value.listens = createModel.value.listens.filter((item) => item !== '')
|
||||
// 端口为空自动添加 80 端口
|
||||
if (createModel.value.listens.length === 0) {
|
||||
createModel.value.listens.push('80')
|
||||
}
|
||||
// 端口中去掉 443 端口,nginx 不允许在未配置证书下监听 443 端口
|
||||
createModel.value.listens = createModel.value.listens.filter((item) => item !== '443')
|
||||
useRequest(website.create(createModel.value)).onSuccess(() => {
|
||||
refresh()
|
||||
window.$message.success(
|
||||
$gettext('Website %{ name } created successfully', { name: createModel.value.name })
|
||||
)
|
||||
createModal.value = false
|
||||
createModel.value = {
|
||||
name: '',
|
||||
domains: [] as Array<string>,
|
||||
listens: [] as Array<string>,
|
||||
php: 0,
|
||||
db: false,
|
||||
db_type: '0',
|
||||
db_name: '',
|
||||
db_user: '',
|
||||
db_password: '',
|
||||
path: '',
|
||||
remark: ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const bulkDelete = async () => {
|
||||
if (selectedRowKeys.value.length === 0) {
|
||||
window.$message.info($gettext('Please select the websites to delete'))
|
||||
@@ -334,16 +269,6 @@ const bulkDelete = async () => {
|
||||
window.$message.success($gettext('Deleted successfully'))
|
||||
}
|
||||
|
||||
const formatDbValue = (value: string) => {
|
||||
value = value.replace(/\./g, '_')
|
||||
value = value.replace(/-/g, '_')
|
||||
if (value.length > 16) {
|
||||
value = value.substring(0, 16)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
window.$bus.on('website:refresh', refresh)
|
||||
@@ -394,147 +319,4 @@ onMounted(() => {
|
||||
}"
|
||||
/>
|
||||
</n-flex>
|
||||
<n-modal
|
||||
v-model:show="createModal"
|
||||
:title="$gettext('Create Website')"
|
||||
preset="card"
|
||||
style="width: 60vw"
|
||||
size="huge"
|
||||
:bordered="false"
|
||||
:segmented="false"
|
||||
@close="createModal = false"
|
||||
>
|
||||
<n-form :model="createModel">
|
||||
<n-form-item path="name" :label="$gettext('Website Name')">
|
||||
<n-input
|
||||
v-model:value="createModel.name"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="
|
||||
$gettext(
|
||||
'Recommended to use English for the website name, it cannot be modified after setting'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="11">
|
||||
<n-form-item :label="$gettext('Domain')">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.domains"
|
||||
placeholder="example.com"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="2"></n-col>
|
||||
<n-col :span="11">
|
||||
<n-form-item :label="$gettext('Port')">
|
||||
<n-dynamic-input
|
||||
v-model:value="createModel.listens"
|
||||
placeholder="80"
|
||||
:min="1"
|
||||
show-sort-button
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="11">
|
||||
<n-form-item path="php" :label="$gettext('PHP Version')">
|
||||
<n-select
|
||||
v-model:value="createModel.php"
|
||||
:options="installedDbAndPhp.php"
|
||||
:placeholder="$gettext('Select PHP Version')"
|
||||
@keydown.enter.prevent
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="2"></n-col>
|
||||
<n-col :span="11">
|
||||
<n-form-item path="db" :label="$gettext('Database')">
|
||||
<n-select
|
||||
v-model:value="createModel.db_type"
|
||||
:options="installedDbAndPhp.db"
|
||||
:placeholder="$gettext('Select Database')"
|
||||
@keydown.enter.prevent
|
||||
@update:value="
|
||||
() => {
|
||||
createModel.db = createModel.db_type != '0'
|
||||
createModel.db_name = formatDbValue(createModel.name)
|
||||
createModel.db_user = formatDbValue(createModel.name)
|
||||
createModel.db_password = generateRandomString(16)
|
||||
}
|
||||
"
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-row :gutter="[0, 24]">
|
||||
<n-col :span="7">
|
||||
<n-form-item v-if="createModel.db" path="db_name" :label="$gettext('Database Name')">
|
||||
<n-input
|
||||
v-model:value="createModel.db_name"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Database Name')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="1"></n-col>
|
||||
<n-col :span="7">
|
||||
<n-form-item v-if="createModel.db" path="db_user" :label="$gettext('Database User')">
|
||||
<n-input
|
||||
v-model:value="createModel.db_user"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Database User')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="1"></n-col>
|
||||
<n-col :span="8">
|
||||
<n-form-item
|
||||
v-if="createModel.db"
|
||||
path="db_password"
|
||||
:label="$gettext('Database Password')"
|
||||
>
|
||||
<n-input
|
||||
v-model:value="createModel.db_password"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Database Password')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
<n-form-item path="path" :label="$gettext('Directory')">
|
||||
<n-input
|
||||
v-model:value="createModel.path"
|
||||
type="text"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="
|
||||
$gettext(
|
||||
'Website root directory (if left empty, defaults to website directory/website name)'
|
||||
)
|
||||
"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item path="remark" :label="$gettext('Remark')">
|
||||
<n-input
|
||||
v-model:value="createModel.remark"
|
||||
type="textarea"
|
||||
@keydown.enter.prevent
|
||||
:placeholder="$gettext('Remark')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button type="info" block @click="handleCreate">
|
||||
{{ $gettext('Create') }}
|
||||
</n-button>
|
||||
</n-modal>
|
||||
<bulk-create v-model:show="bulkCreateModal" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user