feat: 增加创建 php 运行环境功能

pull/515/head
zhengkunwang223 2 years ago committed by zhengkunwang223
parent 1949be2490
commit 64a954df53

@ -76,9 +76,10 @@ func (b *BaseApi) GetApp(c *gin.Context) {
// @Accept json
// @Param appId path integer true "app id"
// @Param version path string true "app 版本"
// @Param version path string true "app 类型"
// @Success 200 {object} response.AppDetailDTO
// @Security ApiKeyAuth
// @Router /apps/detail/:appId/:version [get]
// @Router /apps/detail/:appId/:version/:type [get]
func (b *BaseApi) GetAppDetail(c *gin.Context) {
appId, err := helper.GetIntParamByKey(c, "appId")
if err != nil {
@ -86,7 +87,8 @@ func (b *BaseApi) GetAppDetail(c *gin.Context) {
return
}
version := c.Param("version")
appDetailDTO, err := appService.GetAppDetail(appId, version)
appType := c.Param("type")
appDetailDTO, err := appService.GetAppDetail(appId, version, appType)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return

@ -32,3 +32,25 @@ func (b *BaseApi) SearchRuntimes(c *gin.Context) {
Items: items,
})
}
// @Tags Runtime
// @Summary Create runtime
// @Description 创建运行环境
// @Accept json
// @Param request body request.RuntimeCreate true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /runtimes [post]
// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFuntions":[],"formatZH":"创建运行环境 [name]","formatEN":"Create runtime [name]"}
func (b *BaseApi) CreateRuntime(c *gin.Context) {
var req request.RuntimeCreate
if err := c.ShouldBindJSON(&req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
if err := runtimeService.Create(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}

@ -7,3 +7,13 @@ type RuntimeSearch struct {
Type string `json:"type"`
Name string `json:"name"`
}
type RuntimeCreate struct {
AppDetailID uint `json:"appDetailId"`
Name string `json:"name"`
Params map[string]interface{} `json:"params"`
Resource string `json:"resource"`
Image string `json:"image"`
Type string `json:"type"`
Version string `json:"version"`
}

@ -4,10 +4,14 @@ type Runtime struct {
BaseModel
Name string `gorm:"type:varchar;not null" json:"name"`
AppDetailID uint `gorm:"type:integer" json:"appDetailId"`
Image string `gorm:"type:varchar;not null" json:"image"`
WorkDir string `gorm:"type:varchar;not null" json:"workDir"`
DockerCompose string `gorm:"type:varchar;not null" json:"dockerCompose"`
Env string `gorm:"type:varchar;not null" json:"env"`
Params string `gorm:"type:varchar;not null" json:"params"`
Image string `gorm:"type:varchar" json:"image"`
WorkDir string `gorm:"type:varchar" json:"workDir"`
DockerCompose string `gorm:"type:varchar" json:"dockerCompose"`
Env string `gorm:"type:varchar" json:"env"`
Params string `gorm:"type:varchar" json:"params"`
Version string `gorm:"type:varchar;not null" json:"version"`
Type string `gorm:"type:varchar;not null" json:"type"`
Status string `gorm:"type:varchar;not null" json:"status"`
Resource string `gorm:"type:varchar;not null" json:"resource"`
Message string `gorm:"type:longtext;" json:"message"`
}

@ -58,7 +58,7 @@ func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, err
db := getDb(opts...).Model(&model.App{})
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
err := db.Debug().Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error
return count, apps, err
}

@ -32,7 +32,7 @@ type IAppService interface {
PageApp(req request.AppSearch) (interface{}, error)
GetAppTags() ([]response.TagDTO, error)
GetApp(key string) (*response.AppDTO, error)
GetAppDetail(appId uint, version string) (response.AppDetailDTO, error)
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
Install(ctx context.Context, req request.AppInstallCreate) (*model.AppInstall, error)
SyncAppList() error
GetAppUpdate() (*response.AppUpdateRes, error)
@ -138,7 +138,7 @@ func (a AppService) GetApp(key string) (*response.AppDTO, error) {
return &appDTO, nil
}
func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetailDTO, error) {
func (a AppService) GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) {
var (
appDetailDTO response.AppDetailDTO
opts []repo.DBOption
@ -148,14 +148,36 @@ func (a AppService) GetAppDetail(appId uint, version string) (response.AppDetail
if err != nil {
return appDetailDTO, err
}
paramMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(detail.Params), &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.AppDetail = detail
appDetailDTO.Params = paramMap
appDetailDTO.Enable = true
if appType == "runtime" {
app, err := appRepo.GetFirst(commonRepo.WithByID(appId))
if err != nil {
return appDetailDTO, err
}
paramsPath := path.Join(constant.AppResourceDir, app.Key, "versions", detail.Version, "build", "config.json")
fileOp := files.NewFileOp()
if !fileOp.Stat(paramsPath) {
return appDetailDTO, buserr.New(constant.ErrFileNotExist)
}
param, err := fileOp.GetContent(paramsPath)
if err != nil {
return appDetailDTO, err
}
paramMap := make(map[string]interface{})
if err := json.Unmarshal(param, &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap
} else {
paramMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(detail.Params), &paramMap); err != nil {
return appDetailDTO, err
}
appDetailDTO.Params = paramMap
}
app, err := appRepo.GetFirst(commonRepo.WithByID(detail.AppId))
if err != nil {
return appDetailDTO, err

@ -1,9 +1,17 @@
package service
import (
"context"
"github.com/1Panel-dev/1Panel/backend/app/dto/request"
"github.com/1Panel-dev/1Panel/backend/app/dto/response"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"github.com/1Panel-dev/1Panel/backend/buserr"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/utils/docker"
"github.com/1Panel-dev/1Panel/backend/utils/files"
"github.com/subosito/gotenv"
"path"
)
type RuntimeService struct {
@ -11,14 +19,96 @@ type RuntimeService struct {
type IRuntimeService interface {
Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error)
Create(create request.RuntimeCreate) error
}
func NewRuntimeService() IRuntimeService {
return &RuntimeService{}
}
func (r *RuntimeService) Create() {
func (r *RuntimeService) Create(create request.RuntimeCreate) error {
if create.Resource == constant.ResourceLocal {
runtime := &model.Runtime{
Name: create.Name,
Resource: create.Resource,
Type: create.Type,
Status: constant.RuntimeNormal,
}
return runtimeRepo.Create(context.Background(), runtime)
}
var err error
appDetail, err := appDetailRepo.GetFirst(commonRepo.WithByID(create.AppDetailID))
if err != nil {
return err
}
app, err := appRepo.GetFirst(commonRepo.WithByID(appDetail.AppId))
if err != nil {
return err
}
fileOp := files.NewFileOp()
buildDir := path.Join(constant.AppResourceDir, app.Key, "versions", appDetail.Version, "build")
if !fileOp.Stat(buildDir) {
return buserr.New(constant.ErrDirNotFound)
}
tempDir := path.Join(constant.RuntimeDir, app.Key)
if err := fileOp.CopyDir(buildDir, tempDir); err != nil {
return err
}
oldDir := path.Join(tempDir, "build")
newNameDir := path.Join(tempDir, create.Name)
defer func(defErr *error) {
if defErr != nil {
_ = fileOp.DeleteDir(newNameDir)
}
}(&err)
if oldDir != newNameDir {
if err := fileOp.Rename(oldDir, newNameDir); err != nil {
return err
}
}
composeFile, err := fileOp.GetContent(path.Join(newNameDir, "docker-compose.yml"))
if err != nil {
return err
}
env, err := gotenv.Read(path.Join(newNameDir, ".env"))
if err != nil {
return err
}
newMap := make(map[string]string)
handleMap(create.Params, newMap)
for k, v := range newMap {
env[k] = v
}
envStr, err := gotenv.Marshal(env)
if err != nil {
return err
}
if err := gotenv.Write(env, path.Join(newNameDir, ".env")); err != nil {
return err
}
project, err := docker.GetComposeProject(create.Name, newNameDir, composeFile, []byte(envStr))
if err != nil {
return err
}
composeService, err := docker.NewComposeService()
if err != nil {
return err
}
composeService.SetProject(project)
if err := composeService.ComposeBuild(); err != nil {
return err
}
runtime := &model.Runtime{
Name: create.Name,
DockerCompose: string(composeFile),
Env: envStr,
AppDetailID: create.AppDetailID,
Type: create.Type,
Image: create.Image,
Resource: create.Resource,
Status: constant.RuntimeNormal,
}
return runtimeRepo.Create(context.Background(), runtime)
}
func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeRes, error) {

@ -62,7 +62,7 @@ var (
Err1PanelNetworkFailed = "Err1PanelNetworkFailed"
)
//website
// website
var (
ErrDomainIsExist = "ErrDomainIsExist"
ErrAliasIsExist = "ErrAliasIsExist"
@ -70,7 +70,7 @@ var (
ErrGroupIsUsed = "ErrGroupIsUsed"
)
//ssl
// ssl
var (
ErrSSLCannotDelete = "ErrSSLCannotDelete"
ErrAccountCannotDelete = "ErrAccountCannotDelete"
@ -78,7 +78,7 @@ var (
ErrEmailIsExist = "ErrEmailIsExist"
)
//file
// file
var (
ErrPathNotFound = "ErrPathNotFound"
ErrMovePathFailed = "ErrMovePathFailed"
@ -87,19 +87,26 @@ var (
ErrFileUpload = "ErrFileUpload"
)
//mysql
// mysql
var (
ErrUserIsExist = "ErrUserIsExist"
ErrDatabaseIsExist = "ErrDatabaseIsExist"
)
//redis
// redis
var (
ErrTypeOfRedis = "ErrTypeOfRedis"
)
//container
// container
var (
ErrInUsed = "ErrInUsed"
ErrObjectInUsed = "ErrObjectInUsed"
)
//runtime
var (
ErrDirNotFound = "ErrDirNotFound"
ErrFileNotExist = "ErrFileNotExist"
)

@ -0,0 +1,10 @@
package constant
const (
ResourceLocal = "Local"
ResourceAppstore = "Appstore"
RuntimeNormal = "Normal"
RuntimeBuildSuccess = "BuildSuccess"
RuntimeBuildFailed = "BuildFailed"
)

@ -58,4 +58,8 @@ ErrTypeOfRedis: "The recovery file type does not match the current persistence m
#container
ErrInUsed: "{{ .detail }} is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
ErrObjectInUsed: "This object is in use and cannot be deleted"
#runtime
ErrDirNotFound: "The build folder does not exist! Please check file integrity"
ErrFileNotExist: "{{ .detail }} file does not exist! Please check source file integrity"

@ -58,4 +58,8 @@ ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后
#container
ErrInUsed: "{{ .detail }} 正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
ErrObjectInUsed: "该对象正被使用,无法删除"
#runtime
ErrDirNotFound: "build 文件夹不存在!请检查文件完整性!"
ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!"

@ -14,8 +14,9 @@ func Init() {
constant.ResourceDir = path.Join(constant.DataDir, "resource")
constant.AppResourceDir = path.Join(constant.ResourceDir, "apps")
constant.AppInstallDir = path.Join(constant.DataDir, "apps")
constant.RuntimeDir = path.Join(constant.DataDir, "runtime")
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir, global.CONF.System.Backup}
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir, global.CONF.System.Backup, constant.RuntimeDir}
fileOp := files.NewFileOp()
for _, dir := range dirs {

@ -249,7 +249,7 @@ var AddDefaultGroup = &gormigrate.Migration{
}
var AddTableRuntime = &gormigrate.Migration{
ID: "20230328-add-table-runtime",
ID: "20230330-add-table-runtime",
Migrate: func(tx *gorm.DB) error {
return tx.AutoMigrate(&model.Runtime{})
},

@ -19,7 +19,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
appRouter.GET("/checkupdate", baseApi.GetAppListUpdate)
appRouter.POST("/search", baseApi.SearchApp)
appRouter.GET("/:key", baseApi.GetApp)
appRouter.GET("/detail/:appId/:version", baseApi.GetAppDetail)
appRouter.GET("/detail/:appId/:version/:type", baseApi.GetAppDetail)
appRouter.POST("/install", baseApi.InstallApp)
appRouter.GET("/tags", baseApi.GetAppTags)
appRouter.GET("/installed/:appInstallId/versions", baseApi.GetUpdateVersions)

@ -16,5 +16,6 @@ func (r *RuntimeRouter) InitRuntimeRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
groupRouter.POST("/search", baseApi.SearchRuntimes)
groupRouter.POST("", baseApi.CreateRuntime)
}
}

@ -83,6 +83,10 @@ func (s *ComposeService) ComposeCreate() error {
return s.Create(context.Background(), s.project, api.CreateOptions{})
}
func (s *ComposeService) ComposeBuild() error {
return s.Build(context.Background(), s.project, api.BuildOptions{})
}
func GetComposeProject(projectName, workDir string, yml []byte, env []byte) (*types.Project, error) {
var configFiles []types.ConfigFile
configFiles = append(configFiles, types.ConfigFile{

@ -37,6 +37,15 @@ func (f FileOp) OpenFile(dst string) (fs.File, error) {
return f.Fs.Open(dst)
}
func (f FileOp) GetContent(dst string) ([]byte, error) {
afs := &afero.Afero{Fs: f.Fs}
cByte, err := afs.ReadFile(dst)
if err != nil {
return nil, err
}
return cByte, nil
}
func (f FileOp) CreateDir(dst string, mode fs.FileMode) error {
return f.Fs.MkdirAll(dst, mode)
}

@ -64,6 +64,7 @@ export namespace App {
values?: ServiceParam[];
child?: FromFieldChild;
params?: FromParam[];
multiple?: boolean;
}
export interface FromFieldChild extends FromField {

@ -18,4 +18,15 @@ export namespace Runtime {
export interface RuntimeDTO extends Runtime {
websites: string[];
}
export interface RuntimeCreate {
name: string;
appDetailId: number;
image: string;
params: object;
type: string;
resource: string;
appId?: number;
version?: string;
}
}

@ -22,8 +22,8 @@ export const GetAppTags = () => {
return http.get<App.Tag[]>('apps/tags');
};
export const GetAppDetail = (id: number, version: string) => {
return http.get<App.AppDetail>(`apps/detail/${id}/${version}`);
export const GetAppDetail = (id: number, version: string, type: string) => {
return http.get<App.AppDetail>(`apps/detail/${id}/${version}/${type}`);
};
export const InstallApp = (install: App.AppInstall) => {

@ -5,3 +5,7 @@ import { Runtime } from '../interface/runtime';
export const SearchRuntimes = (req: Runtime.RuntimeReq) => {
return http.post<ResPage<Runtime.RuntimeDTO>>(`/runtimes/search`, req);
};
export const CreateRuntime = (req: Runtime.RuntimeCreate) => {
return http.post<any>(`/runtimes`, req);
};

@ -1228,6 +1228,14 @@ const message = {
runtime: '',
image: '',
workDir: '',
create: '',
name: '',
resource: '',
appStore: '',
local: '',
app: '',
localHelper: '',
version: '',
},
};
export default {

@ -40,7 +40,7 @@ const webSiteRouter = {
},
},
{
path: '/websites/runtime',
path: '/websites/runtime/php',
name: 'Runtime',
component: () => import('@/views/website/runtime/index.vue'),
meta: {

@ -131,7 +131,7 @@ const getApp = async () => {
const getDetail = async (id: number, version: string) => {
loadingDetail.value = true;
try {
const res = await GetAppDetail(id, version);
const res = await GetAppDetail(id, version, 'app');
appDetail.value = res.data;
} finally {
loadingDetail.value = false;

@ -0,0 +1,190 @@
<template>
<el-drawer :close-on-click-modal="false" v-model="open" size="50%">
<template #header>
<DrawerHeader :header="$t('runtime.create')" :back="handleClose" />
</template>
<el-row v-loading="loading">
<el-col :span="22" :offset="1">
<el-form
ref="runtimeForm"
label-position="top"
:model="runtimeCreate"
label-width="125px"
:rules="rules"
:validate-on-rule-change="false"
>
<el-form-item :label="$t('runtime.name')" prop="name">
<el-input v-model="runtimeCreate.name"></el-input>
</el-form-item>
<el-form-item :label="$t('runtime.resource')" prop="resource">
<el-radio-group
v-model="runtimeCreate.resource"
@change="changeResource(runtimeCreate.resource)"
>
<el-radio :label="'AppStore'" :value="'AppStore'">
{{ $t('runtime.appStore') }}
</el-radio>
<el-radio :label="'Local'" :value="'Local'">
{{ $t('runtime.local') }}
</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="runtimeCreate.resource === 'AppStore'">
<el-form-item :label="$t('runtime.app')" prop="appId">
<el-row :gutter="20">
<el-col :span="12">
<el-select v-model="runtimeCreate.appId">
<el-option
v-for="(app, index) in apps"
:key="index"
:label="app.name"
:value="app.id"
></el-option>
</el-select>
</el-col>
<el-col :span="12">
<el-select v-model="runtimeCreate.version">
<el-option
v-for="(version, index) in appVersions"
:key="index"
:label="version"
:value="version"
></el-option>
</el-select>
</el-col>
</el-row>
</el-form-item>
<Params
v-if="initParam"
v-model:form="runtimeCreate"
v-model:params="appParams"
v-model:rules="rules"
></Params>
</div>
<div v-else>
<el-alert :title="$t('runtime.localHelper')" type="info" :closable="false" />
<el-form-item :label="$t('runtime.version')" prop="version">
<el-input v-model="runtimeCreate.version"></el-input>
</el-form-item>
</div>
</el-form>
</el-col>
</el-row>
<template #footer>
<span>
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
<el-button type="primary" @click="submit(runtimeForm)" :disabled="loading">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { App } from '@/api/interface/app';
import { Runtime } from '@/api/interface/runtime';
import { GetApp, GetAppDetail, SearchApp } from '@/api/modules/app';
import { CreateRuntime } from '@/api/modules/runtime';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { FormInstance } from 'element-plus';
import { reactive, ref } from 'vue';
import Params from '../param/index.vue';
const open = ref(false);
const apps = ref<App.App[]>([]);
const runtimeForm = ref<FormInstance>();
const loading = ref(false);
const initParam = ref(false);
let appParams = ref<App.AppParams>();
let appVersions = ref<string[]>([]);
let appReq = reactive({
type: 'php',
page: 1,
pageSize: 20,
});
const runtimeCreate = ref<Runtime.RuntimeCreate>({
name: '',
appDetailId: undefined,
image: '',
params: {},
type: '',
resource: 'AppStore',
});
let rules = ref<any>({
name: [Rules.appName],
resource: [Rules.requiredInput],
appId: [Rules.requiredSelect],
version: [Rules.requiredInput],
});
const em = defineEmits(['close']);
const handleClose = () => {
open.value = false;
em('close', false);
};
const changeResource = (resource: string) => {
if (resource === 'Local') {
runtimeCreate.value.appDetailId = undefined;
runtimeCreate.value.version = '';
runtimeCreate.value.params = {};
runtimeCreate.value.image = '';
} else {
runtimeCreate.value.version = '';
}
};
const searchApp = () => {
SearchApp(appReq).then((res) => {
apps.value = res.data.items || [];
if (res.data && res.data.items && res.data.items.length > 0) {
runtimeCreate.value.appId = res.data.items[0].id;
getApp(res.data.items[0].key);
}
});
};
const getApp = (appkey: string) => {
GetApp(appkey).then((res) => {
appVersions.value = res.data.versions || [];
if (res.data.versions.length > 0) {
runtimeCreate.value.version = res.data.versions[0];
GetAppDetail(runtimeCreate.value.appId, runtimeCreate.value.version, 'runtime').then((res) => {
runtimeCreate.value.appDetailId = res.data.id;
appParams.value = res.data.params;
initParam.value = true;
});
}
});
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (!valid) {
return;
}
loading.value = true;
CreateRuntime(runtimeCreate.value)
.then(() => {
MsgSuccess(i18n.global.t('commons.msg.createSuccess'));
handleClose();
})
.finally(() => {
loading.value = false;
});
});
};
const acceptParams = async () => {
searchApp();
open.value = true;
};
defineExpose({
acceptParams,
});
</script>

@ -4,12 +4,16 @@
:buttons="[
{
label: 'PHP',
path: '/runtimes',
path: '/runtimes/php',
},
]"
/>
<LayoutContent :title="$t('runtime.runtime')" v-loading="loading">
<template #toolbar></template>
<template #toolbar>
<el-button type="primary" @click="openCreate">
{{ $t('runtime.create') }}
</el-button>
</template>
<template #main>
<ComplexTable :pagination-config="paginationConfig" :data="items" @search="search()">
<el-table-column :label="$t('commons.table.name')" fix prop="name" min-width="120px">
@ -28,6 +32,7 @@
</ComplexTable>
</template>
</LayoutContent>
<CreateRuntime ref="createRef" />
</div>
</template>
@ -35,10 +40,11 @@
import { onMounted, reactive, ref } from 'vue';
import { Runtime } from '@/api/interface/runtime';
import { SearchRuntimes } from '@/api/modules/runtime';
import { dateFormat } from '@/utils/util';
import RouterButton from '@/components/router-button/index.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import LayoutContent from '@/layout/layout-content.vue';
import { dateFormat } from '@/utils/util';
import CreateRuntime from '@/views/website/runtime/create/index.vue';
const paginationConfig = reactive({
currentPage: 1,
@ -52,6 +58,7 @@ let req = reactive<Runtime.RuntimeReq>({
});
const loading = ref(false);
const items = ref<Runtime.RuntimeDTO[]>([]);
const createRef = ref();
const search = async () => {
req.page = paginationConfig.currentPage;
@ -67,6 +74,10 @@ const search = async () => {
}
};
const openCreate = () => {
createRef.value.acceptParams();
};
onMounted(() => {
search();
});

@ -0,0 +1,94 @@
<template>
<div v-for="(p, index) in paramObjs" :key="index">
<el-form-item :label="getLabel(p)" :prop="p.prop">
<el-select v-model="form[p.envKey]" v-if="p.type == 'select'" :multiple="p.multiple">
<el-option
v-for="service in p.values"
:key="service.label"
:value="service.value"
:label="service.label"
></el-option>
</el-select>
</el-form-item>
</div>
</template>
<script setup lang="ts">
import { App } from '@/api/interface/app';
// import { Rules } from '@/global/form-rules';
import { computed, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
interface ParamObj extends App.FromField {
services: App.AppService[];
prop: string;
disabled: false;
childProp: string;
}
const props = defineProps({
form: {
type: Object,
default: function () {
return {};
},
},
params: {
type: Object,
default: function () {
return {};
},
},
rules: {
type: Object,
default: function () {
return {};
},
},
});
let form = reactive({});
// let rules = reactive({});
const params = computed({
get() {
return props.params;
},
set() {},
});
const paramObjs = ref<ParamObj[]>([]);
const handleParams = () => {
// rules = props.rules;
form = props.form;
if (params.value != undefined && params.value.formFields != undefined) {
for (const p of params.value.formFields) {
const pObj = p;
pObj.prop = p.envKey;
pObj.disabled = p.disabled;
form[p.envKey] = p.default;
paramObjs.value.push(pObj);
// if (p.required) {
// if (p.type === 'select') {
// rules[p.envKey] = [Rules.requiredSelect];
// } else {
// rules[p.envKey] = [Rules.requiredInput];
// }
// }
}
console.log(form);
}
};
const getLabel = (row: ParamObj): string => {
const language = useI18n().locale.value;
if (language == 'zh') {
return row.labelZh;
} else {
return row.labelEn;
}
};
onMounted(() => {
handleParams();
});
</script>

@ -310,7 +310,7 @@ const getApp = () => {
};
const getAppDetail = (version: string) => {
GetAppDetail(website.value.appinstall.appId, version).then((res) => {
GetAppDetail(website.value.appinstall.appId, version, 'app').then((res) => {
website.value.appinstall.appDetailId = res.data.id;
appDetail.value = res.data;
appParams.value = res.data.params;

Loading…
Cancel
Save