feat: 优化应用安装流程 (#2168)

This commit is contained in:
zhengkunwang 2023-09-04 15:58:13 +08:00 committed by GitHub
parent dbf349ee9d
commit 5b00bfd1c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 197 additions and 141 deletions

View file

@ -136,9 +136,6 @@ const goRouter = async (key: string) => {
const onCheck = async () => {
await CheckAppInstalled(key.value, name.value)
.then((res) => {
if (res.data.appInstallId === 0) {
return;
}
data.value = res.data;
em('isExist', res.data);
operateReq.installId = res.data.appInstallId;

View file

@ -40,7 +40,9 @@
</template>
<template #rightButton>
<el-badge is-dot class="item" :hidden="!canUpdate">
<el-button @click="sync" type="primary" link :plain="true">{{ $t('app.syncAppList') }}</el-button>
<el-button @click="sync" type="primary" link :plain="true" :disabled="syncing">
{{ $t('app.syncAppList') }}
</el-button>
</el-badge>
</template>
<template #main>
@ -60,13 +62,15 @@
<el-card class="e-card">
<el-row :gutter="20">
<el-col :xs="8" :sm="6" :md="6" :lg="6" :xl="5">
<div class="app-icon">
<div class="app-icon-container">
<div class="app-icon" @click="openDetail(app.key)">
<el-avatar
shape="square"
:size="60"
:src="'data:image/png;base64,' + app.icon"
/>
</div>
</div>
</el-col>
<el-col :xs="16" :sm="18" :md="18" :lg="18" :xl="19">
<div class="app-content">
@ -81,7 +85,7 @@
plain
round
size="small"
@click="getAppDetail(app.key)"
@click="openInstall(app)"
:disabled="app.status === 'TakeDown'"
>
{{ $t('app.install') }}
@ -124,7 +128,8 @@
</div>
</template>
</LayoutContent>
<Detail v-if="showDetail" :id="appId"></Detail>
<Detail :id="appId" ref="detailRef"></Detail>
<Install ref="installRef" />
</template>
<script lang="ts" setup>
@ -133,6 +138,7 @@ import { onMounted, reactive, ref, computed } from 'vue';
import { GetAppTags, SearchApp, SyncApp } from '@/api/modules/app';
import i18n from '@/lang';
import Detail from '../detail/index.vue';
import Install from '../detail/install/index.vue';
import router from '@/routers';
import { MsgSuccess } from '@/utils/message';
import { useI18n } from 'vue-i18n';
@ -167,6 +173,9 @@ const activeTag = ref('all');
const showDetail = ref(false);
const appId = ref(0);
const canUpdate = ref(false);
const syncing = ref(false);
const detailRef = ref();
const installRef = ref();
const getColor = (index: number) => {
return colorArr[index];
@ -189,12 +198,25 @@ const search = async (req: App.AppReq) => {
});
};
const getAppDetail = (key: string) => {
router.push({ name: 'AppDetail', params: { appKey: key } });
const openInstall = (app: App.App) => {
if (app.type === 'php') {
router.push({ path: '/websites/runtime/php' });
} else {
const params = {
app: app,
};
installRef.value.acceptParams(params);
}
};
const openDetail = (key: string) => {
detailRef.value.acceptParams(key);
};
const sync = () => {
SyncApp().then((res) => {
syncing.value = true;
SyncApp()
.then((res) => {
if (res.message != '') {
MsgSuccess(res.message);
} else {
@ -202,6 +224,9 @@ const sync = () => {
}
canUpdate.value = false;
search(req);
})
.finally(() => {
syncing.value = false;
});
};
@ -234,9 +259,18 @@ onMounted(() => {
cursor: pointer;
padding: 5px;
.app-icon {
.app-icon-container {
margin-top: 10px;
margin-left: 10px;
margin-left: 15px;
}
.app-icon {
transition: transform 0.1s;
transform-origin: center center;
}
.app-icon:hover {
transform: scale(1.2);
}
.app-content {

View file

@ -1,6 +1,8 @@
<template>
<LayoutContent :title="$t('app.detail')" :back-name="'App'" :divider="true">
<template #main>
<el-drawer v-model="open" :destroy-on-close="true" size="50%">
<template #header>
<DrawerHeader :header="$t('app.detail')" :back="handleClose" />
</template>
<div class="brief" v-loading="loadingApp">
<el-row :gutter="20">
<div>
@ -18,21 +20,6 @@
{{ language == 'zh' || language == 'tw' ? app.shortDescZh : app.shortDescEn }}
</span>
</div>
<div class="version">
<el-form-item :label="$t('app.version')">
<el-select v-model="version" @change="getDetail(app.id, version)">
<el-option
v-for="(v, index) in app.versions"
:key="index"
:value="v"
:label="v"
>
{{ v }}
</el-option>
</el-select>
</el-form-item>
</div>
<br />
<div v-if="!loadingDetail">
<el-alert
@ -82,22 +69,19 @@
</el-row>
</div>
</div>
<div style="margin-left: 10px">
<MdEditor
v-model="app.readMe"
previewOnly
v-model="app.readMe"
:theme="globalStore.$state.themeConfig.theme === 'dark' ? 'dark' : 'light'"
/>
</div>
</template>
</LayoutContent>
<Install ref="installRef"></Install>
</el-drawer>
</template>
<script lang="ts" setup>
import { GetApp, GetAppDetail } from '@/api/modules/app';
import MdEditor from 'md-editor-v3';
import { onMounted, ref } from 'vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Install from './install/index.vue';
import router from '@/routers';
@ -106,25 +90,29 @@ const globalStore = GlobalStore();
const language = useI18n().locale.value;
interface OperateProps {
appKey: string;
}
const props = withDefaults(defineProps<OperateProps>(), {
// id: 0,
appKey: '',
});
const app = ref<any>({});
const appDetail = ref<any>({});
const version = ref('');
const loadingDetail = ref(false);
const loadingApp = ref(false);
const installRef = ref();
const open = ref(false);
const appKey = ref();
const acceptParams = async (key: string) => {
appKey.value = key;
open.value = true;
getApp();
};
const handleClose = () => {
open.value = false;
};
const getApp = async () => {
loadingApp.value = true;
try {
const res = await GetApp(props.appKey);
const res = await GetApp(appKey.value);
app.value = res.data;
app.value.icon = 'data:image/png;base64,' + res.data.icon;
version.value = app.value.versions[0];
@ -149,26 +137,24 @@ const toLink = (link: string) => {
};
const openInstall = () => {
let params = {
params: appDetail.value.params,
appDetailId: appDetail.value.id,
app: app.value,
compose: appDetail.value.dockerCompose,
};
if (app.value.type === 'php') {
router.push({ path: '/websites/runtime/php' });
} else {
const params = {
app: app.value,
};
installRef.value.acceptParams(params);
}
};
onMounted(() => {
getApp();
defineExpose({
acceptParams,
});
</script>
<style lang="scss">
.brief {
padding: 10px;
.name {
span {
font-weight: 500;
@ -184,6 +170,9 @@ onMounted(() => {
}
}
.detail {
}
.version {
margin-top: 10px;
}

View file

@ -24,7 +24,18 @@
<el-form-item :label="$t('commons.table.name')" prop="name">
<el-input v-model.trim="req.name"></el-input>
</el-form-item>
<el-form-item :label="$t('app.version')" prop="version">
<el-select v-model="req.version" @change="getAppDetail(req.version)">
<el-option
v-for="(version, index) in appVersions"
:key="index"
:label="version"
:value="version"
></el-option>
</el-select>
</el-form-item>
<Params
:key="paramKey"
v-if="open"
v-model:form="req.params"
v-model:params="installData.params"
@ -116,7 +127,7 @@
<script lang="ts" setup name="appInstall">
import { App } from '@/api/interface/app';
import { InstallApp } from '@/api/modules/app';
import { GetApp, GetAppDetail, InstallApp } from '@/api/modules/app';
import { Rules, checkNumberRange } from '@/global/form-rules';
import { canEditPort } from '@/global/business';
import { FormInstance, FormRules } from 'element-plus';
@ -136,21 +147,18 @@ const extensions = [javascript(), oneDark];
const router = useRouter();
interface InstallRrops {
appDetailId: number;
params?: App.AppParams;
app: any;
compose: string;
}
const installData = ref<InstallRrops>({
appDetailId: 0,
app: {},
compose: '',
});
const open = ref(false);
const rules = ref<FormRules>({
name: [Rules.appName],
params: [],
version: [Rules.requiredSelect],
containerName: [Rules.containerName],
cpuQuota: [Rules.requiredInput, checkNumberRange(0, 99999)],
memoryLimit: [Rules.requiredInput, checkNumberRange(0, 9999999999)],
@ -170,6 +178,8 @@ const initData = () => ({
allowPort: false,
editCompose: false,
dockerCompose: '',
version: '',
appID: '',
});
const req = reactive(initData());
const limits = ref<Container.ResourceLimit>({
@ -177,11 +187,12 @@ const limits = ref<Container.ResourceLimit>({
memory: null as number,
});
const oldMemory = ref<number>(0);
const appVersions = ref<string[]>([]);
const handleClose = () => {
open.value = false;
resetForm();
};
const paramKey = ref(1);
const changeUnit = () => {
if (req.memoryUnit == 'M') {
@ -197,16 +208,42 @@ const resetForm = () => {
paramForm.value.resetFields();
}
Object.assign(req, initData());
req.dockerCompose = installData.value.compose;
};
const acceptParams = (props: InstallRrops): void => {
installData.value = props;
const acceptParams = async (props: InstallRrops) => {
resetForm();
if (props.app.versions != undefined) {
installData.value = props;
} else {
const res = await GetApp(props.app.key);
installData.value.app = res.data;
}
const app = installData.value.app;
appVersions.value = app.versions;
if (appVersions.value.length > 0) {
req.version = appVersions.value[0];
getAppDetail(appVersions.value[0]);
}
req.name = props.app.key;
open.value = true;
};
const getAppDetail = async (version: string) => {
loading.value = true;
try {
const res = await GetAppDetail(installData.value.app.id, version, 'app');
req.appDetailId = res.data.id;
req.dockerCompose = res.data.dockerCompose;
installData.value.params = res.data.params;
paramKey.value++;
} catch (error) {
} finally {
loading.value = false;
}
};
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid) => {
@ -217,7 +254,6 @@ const submit = async (formEl: FormInstance | undefined) => {
MsgError(i18n.global.t('app.composeNullErr'));
return;
}
req.appDetailId = installData.value.appDetailId;
if (req.cpuQuota < 0) {
req.cpuQuota = 0;
}