1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-01-19 16:29:17 +08:00

style: 应用商店样式调整 (#5993)

This commit is contained in:
zhengkunwang 2024-08-01 10:59:43 +08:00 committed by GitHub
parent 256c4ceecc
commit 82d8997217
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 127 additions and 300 deletions

View File

@ -13,6 +13,8 @@ export namespace App {
type: string;
status: string;
limit: number;
website: string;
github: string;
}
export interface AppDTO extends App {

View File

@ -60,14 +60,6 @@
</el-badge>
</template>
<template #rightToolBar>
<!-- <fu-table-pagination
v-model:current-page="paginationConfig.currentPage"
v-model:page-size="paginationConfig.pageSize"
v-bind="paginationConfig"
@change="search(req)"
:layout="mobile ? ' prev, pager, next' : ' prev, pager, next'"
class="mr-2.5"
/> -->
<el-checkbox
class="!mr-2.5"
v-model="req.resource"
@ -92,46 +84,46 @@
:sm="12"
:md="8"
:lg="8"
:xl="8"
:xl="6"
>
<div class="app-card">
<el-card class="e-card" @click.stop="openDetail(app.key)">
<el-row :gutter="20">
<el-col :xs="8" :sm="6" :md="6" :lg="6" :xl="5">
<div class="app-icon-container">
<div class="app-icon">
<div class="app">
<el-card>
<div class="app-wrapper">
<div class="app-image">
<el-popover placement="top-start" :width="200" trigger="hover">
<template #reference>
<el-avatar
shape="square"
:size="60"
:src="'data:image/png;base64,' + app.icon"
/>
</template>
<div>
<el-link @click="toLink(app.website)">
<el-icon><OfficeBuilding /></el-icon>
<span>{{ $t('app.appOfficeWebsite') }}</span>
</el-link>
<el-link class="ml-5" @click="toLink(app.github)">
<el-icon><Link /></el-icon>
<span>{{ $t('app.github') }}</span>
</el-link>
<div class="mt-5">
<el-text>推荐配置: 1c 1g</el-text>
</div>
</div>
</el-col>
<el-col :xs="16" :sm="18" :md="18" :lg="18" :xl="19">
</el-popover>
</div>
<div class="app-content">
<div class="app-header">
<span class="app-title">{{ app.name }}</span>
<div class="content-top">
<div>
<span class="app-name">{{ app.name }}</span>
<el-text type="success" class="!ml-2" v-if="app.installed">
{{ $t('app.allReadyInstalled') }}
</el-text>
<el-button
class="app-button"
type="primary"
plain
round
size="small"
:disabled="
(app.installed && app.limit == 1) ||
app.status === 'TakeDown'
"
@click.stop="openInstall(app)"
>
{{ $t('app.install') }}
</el-button>
</div>
<div class="app-desc">
<span class="desc">
</div>
<div class="content-middle">
<span class="app-description">
{{
language == 'zh' || language == 'tw'
? app.shortDescZh
@ -139,8 +131,9 @@
}}
</span>
</div>
<div class="app-tag">
<el-tag v-for="(tag, ind) in app.tags" :key="ind" class="p-mr-5">
<div class="content-bottom">
<div class="app-tags">
<el-tag v-for="(tag, ind) in app.tags" :key="ind" type="info">
<span>
{{
language == 'zh' || language == 'tw'
@ -153,9 +146,21 @@
<span style="color: red">{{ $t('app.takeDown') }}</span>
</el-tag>
</div>
<el-button
type="primary"
size="small"
plain
round
:disabled="
(app.installed && app.limit == 1) || app.status === 'TakeDown'
"
@click.stop="openInstall(app)"
>
{{ $t('app.install') }}
</el-button>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</div>
</el-col>
@ -174,7 +179,6 @@
</div>
</template>
</LayoutContent>
<Detail ref="detailRef"></Detail>
<Install ref="installRef" />
</template>
@ -183,7 +187,6 @@ import { App } from '@/api/interface/app';
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';
@ -220,7 +223,6 @@ const activeTag = ref('all');
const showDetail = ref(false);
const canUpdate = ref(false);
const syncing = ref(false);
const detailRef = ref();
const installRef = ref();
const installKey = ref('');
const moreTag = ref('');
@ -266,10 +268,6 @@ const openInstall = (app: App.App) => {
}
};
const openDetail = (key: string) => {
detailRef.value.acceptParams(key, 'install');
};
const sync = () => {
syncing.value = true;
SyncApp()
@ -313,6 +311,10 @@ const searchByName = () => {
search(req);
};
const toLink = (link: string) => {
window.open(link, '_blank');
};
onMounted(() => {
if (router.currentRoute.value.query.install) {
installKey.value = String(router.currentRoute.value.query.install);
@ -338,48 +340,68 @@ onMounted(() => {
padding-bottom: 10px;
}
.app-card {
margin-top: 10px;
.app {
margin: 10px;
.el-card {
padding: 0 !important;
border: var(--panel-border) !important;
&:hover {
border: 1px solid var(--el-color-primary) !important;
}
}
.el-card__body {
padding: 8px 8px 2px 8px !important;
}
.app-wrapper {
display: flex;
height: 100%;
}
.app-image {
cursor: pointer;
padding: 5px;
.app-icon-container {
margin-top: 10px;
margin-left: 15px;
flex: 0 0 100px;
display: flex;
justify-content: center;
margin-top: 14px;
transition: transform 0.1s;
}
&:hover .app-icon {
&:hover .app-image {
transform: scale(1.2);
}
.app-icon {
transition: transform 0.1s;
transform-origin: center center;
.el-avatar {
width: 65px !important;
height: 65px !important;
max-width: 65px;
max-height: 65px;
object-fit: cover;
}
.app-content {
margin-top: 10px;
height: 100%;
width: 100%;
.app-header {
height: 20%;
.app-title {
flex: 1;
display: flex;
flex-direction: column;
padding: 10px;
}
.content-top,
.content-bottom {
display: flex;
justify-content: space-between;
align-items: center;
}
.content-middle {
flex: 1;
margin: 10px 0;
overflow: hidden; /* 防止内容溢出 */
}
.app-name {
margin: 0;
line-height: 1.5;
font-weight: 500;
font-size: 16px;
color: var(--el-text-color-regular);
}
.app-button {
float: right;
margin-right: 20px;
}
}
.app-desc {
margin-top: 8px;
height: 43px;
.desc {
.app-description {
margin: 0;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
@ -387,20 +409,14 @@ onMounted(() => {
text-overflow: ellipsis;
font-size: 14px;
color: var(--el-text-color-regular);
}
}
.app-tag {
margin-top: 5px;
}
}
.e-card {
border: var(--panel-border) !important;
&:hover {
cursor: pointer;
border: 1px solid var(--el-color-primary) !important;
line-height: 1.2;
height: calc(1.2em * 2);
min-height: calc(1.2em * 2);
}
.app-tags {
display: flex;
gap: 5px;
}
}

View File

@ -1,191 +0,0 @@
<template>
<DrawerPro v-model="open" :header="$t('app.detail')" :back="handleClose" size="large">
<template #content>
<div class="brief" v-loading="loadingApp">
<div class="detail flex">
<div class="w-12 h-12 rounded p-1 shadow-md icon">
<img :src="app.icon" alt="App Icon" class="w-full h-full rounded" style="object-fit: contain" />
</div>
<div class="ml-4">
<div class="name mb-2">
<span>{{ app.name }}</span>
</div>
<div class="description mb-4">
<span>
{{ language == 'zh' || language == 'tw' ? app.shortDescZh : app.shortDescEn }}
</span>
</div>
<br />
<div v-if="!loadingDetail" class="mb-2">
<el-alert
v-if="!appDetail.enable"
:title="$t('app.limitHelper')"
type="warning"
show-icon
:closable="false"
/>
</div>
<el-button
round
v-if="appDetail.enable && operate === 'install'"
@click="openInstall"
type="primary"
class="brief-button"
>
{{ $t('app.install') }}
</el-button>
</div>
</div>
<div class="divider"></div>
<div class="descriptions">
<div>
<el-descriptions direction="vertical">
<el-descriptions-item>
<div class="icons">
<el-link @click="toLink(app.website)">
<el-icon><OfficeBuilding /></el-icon>
<span>{{ $t('app.appOfficeWebsite') }}</span>
</el-link>
</div>
</el-descriptions-item>
<el-descriptions-item>
<el-link @click="toLink(app.document)">
<el-icon><Document /></el-icon>
<span>{{ $t('app.document') }}</span>
</el-link>
</el-descriptions-item>
<el-descriptions-item>
<el-link @click="toLink(app.github)">
<el-icon><Link /></el-icon>
<span>{{ $t('app.github') }}</span>
</el-link>
</el-descriptions-item>
</el-descriptions>
</div>
</div>
</div>
<MdEditor previewOnly v-model="app.readMe" :theme="isDarkTheme ? 'dark' : 'light'" />
</template>
</DrawerPro>
<Install ref="installRef"></Install>
</template>
<script lang="ts" setup>
import { GetApp, GetAppDetail } from '@/api/modules/app';
import MdEditor from 'md-editor-v3';
import { ref } from 'vue';
import Install from './install/index.vue';
import router from '@/routers';
import { GlobalStore } from '@/store';
import { getLanguage } from '@/utils/util';
import { storeToRefs } from 'pinia';
const globalStore = GlobalStore();
const { isDarkTheme } = storeToRefs(globalStore);
const language = getLanguage();
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 operate = ref();
const acceptParams = async (key: string, op: string) => {
appKey.value = key;
operate.value = op;
open.value = true;
getApp();
};
const handleClose = () => {
open.value = false;
};
const getApp = async () => {
loadingApp.value = true;
try {
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];
getDetail(app.value.id, version.value);
} finally {
loadingApp.value = false;
}
};
const getDetail = async (id: number, version: string) => {
loadingDetail.value = true;
try {
const res = await GetAppDetail(id, version, 'app');
appDetail.value = res.data;
} finally {
loadingDetail.value = false;
}
};
const toLink = (link: string) => {
window.open(link, '_blank');
};
const openInstall = () => {
switch (app.value.type) {
case 'php':
router.push({ path: '/websites/runtimes/php' });
break;
case 'node':
router.push({ path: '/websites/runtimes/node' });
break;
default:
const params = {
app: app.value,
};
installRef.value.acceptParams(params);
open.value = false;
}
};
defineExpose({
acceptParams,
});
</script>
<style lang="scss">
.brief {
.name {
span {
font-weight: 500;
font-size: 18px;
color: var(--el-text-color-regular);
}
}
.description {
margin-top: 10px;
span {
font-size: 14px;
color: var(--el-text-color-regular);
}
}
.icon {
width: 180px;
height: 180px;
}
.version {
margin-top: 10px;
}
.descriptions {
margin-top: 5px;
.icons {
margin-left: 20px;
}
}
}
</style>