feat: 修改应用搜索

This commit is contained in:
zhengkunwang223 2022-11-28 13:50:53 +08:00 committed by zhengkunwang223
parent 8ceb75b195
commit f3d113f051
9 changed files with 264 additions and 162 deletions

View File

@ -64,6 +64,7 @@ type AppInstalled struct {
type AppInstalledRequest struct {
PageInfo
Type string `json:"type"`
Name string `json:"name"`
}
type AppBackupRequest struct {

View File

@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
"github.com/1Panel-dev/1Panel/backend/app/repo"
"io/ioutil"
"os"
"path"
@ -25,7 +26,13 @@ type AppInstallService struct {
}
func (a AppInstallService) Page(req dto.AppInstalledRequest) (int64, []dto.AppInstalled, error) {
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize)
var opts []repo.DBOption
if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name))
}
total, installs, err := appInstallRepo.Page(req.Page, req.PageSize, opts...)
if err != nil {
return 0, nil, err
}

View File

@ -63,6 +63,9 @@ export namespace App {
params: any;
}
export interface AppInstallSearch extends ReqPage {
name?: string;
}
export interface ChangePort {
key: string;
name: string;

View File

@ -1,5 +1,5 @@
import http from '@/api';
import { ReqPage, ResPage } from '../interface';
import { ResPage } from '../interface';
import { App } from '../interface/app';
export const SyncApp = () => {
@ -26,15 +26,15 @@ export const ChangePort = (params: App.ChangePort) => {
return http.post<any>('apps/installed/port/change', params);
};
export const GetAppInstalled = (info: ReqPage) => {
return http.post<ResPage<App.AppInstalled>>('apps/installed', info);
export const SearchAppInstalled = (search: App.AppInstallSearch) => {
return http.post<ResPage<App.AppInstalled>>('apps/installed', search);
};
export const CheckAppInstalled = (key: string) => {
return http.get<App.CheckInstalled>(`apps/installed/check/${key}`);
};
export const SearchAppInstalled = (search: App.AppInstalledSearch) => {
export const GetAppInstalled = (search: App.AppInstalledSearch) => {
return http.post<App.AppInstalled[]>('apps/installed', search);
};

View File

@ -1,17 +1,39 @@
<template>
<div v-loading="loading">
<el-card v-loading="loading">
<el-row :gutter="5">
<el-col :span="12">
<el-input v-model="req.name" @blur="searchByName"></el-input>
</el-col>
<el-col :span="11">
<el-select v-model="req.tags" multiple style="width: 100%" @change="changeTag">
<el-option v-for="item in tags" :key="item.key" :label="item.name" :value="item.key"></el-option>
</el-select>
</el-col>
<el-col :span="1">
<el-col :span="2">
<el-button @click="sync">{{ $t('app.sync') }}</el-button>
</el-col>
<el-col :span="22">
<div style="float: right">
<el-input
style="display: inline; margin-right: 5px"
v-model="req.name"
clearable
@clear="searchByName('')"
></el-input>
<el-button
style="display: inline; margin-right: 5px"
v-model="req.name"
@click="searchByName(req.name)"
>
{{ '搜索' }}
</el-button>
</div>
</el-col>
</el-row>
<br />
<el-row>
<el-button style="margin-right: 5px" @click="changeTag('all')" type="primary" :plain="activeTag !== 'all'">
{{ $t('app.all') }}
</el-button>
<div style="margin-right: 5px" :span="1" v-for="item in tags" :key="item.key">
<el-button @click="changeTag(item.key)" type="primary" :plain="activeTag !== item.key">
{{ item.name }}
</el-button>
</div>
</el-row>
<el-row :gutter="5">
<el-col v-for="(app, index) in apps" :key="index" :span="6">
<div @click="getAppDetail(app.id)">
<el-card :body-style="{ padding: '0px' }" class="a-card">
@ -45,7 +67,7 @@
</div>
</el-col>
</el-row>
</div>
</el-card>
</template>
<script lang="ts" setup>
@ -67,6 +89,7 @@ let apps = ref<App.App[]>([]);
let tags = ref<App.Tag[]>([]);
const colorArr = ['#6495ED', '#54FF9F', '#BEBEBE', '#FFF68F', '#FFFF00', '#8B0000'];
let loading = ref(false);
let activeTag = ref('all');
const getColor = (index: number) => {
return colorArr[index];
@ -98,11 +121,17 @@ const sync = () => {
});
};
const changeTag = () => {
const changeTag = (key: string) => {
req.tags = [];
activeTag.value = key;
if (key !== 'all') {
req.tags = [key];
}
search(req);
};
const searchByName = () => {
const searchByName = (name: string) => {
req.name = name;
search(req);
};

View File

@ -1,53 +1,60 @@
<template>
<LayoutContent :header="$t('app.detail')" :back-name="'App'">
<div class="brief">
<el-row :gutter="20">
<el-col :span="4">
<div class="icon">
<el-image class="image" :src="'data:image/png;base64,' + app.icon"></el-image>
</div>
</el-col>
<el-col :span="20">
<div class="a-detail">
<div class="a-name">
<font size="5" style="font-weight: 800">{{ app.name }}</font>
<el-card>
<LayoutContent :header="$t('app.detail')" :back-name="'App'">
<div class="brief">
<el-row :gutter="20">
<el-col :span="4">
<div class="icon">
<el-image class="image" :src="'data:image/png;base64,' + app.icon"></el-image>
</div>
<div class="a-description">
<span>
<font>
{{ app.shortDesc }}
</font>
</span>
</el-col>
<el-col :span="20">
<div class="a-detail">
<div class="a-name">
<font size="5" style="font-weight: 800">{{ app.name }}</font>
</div>
<div class="a-description">
<span>
<font>
{{ app.shortDesc }}
</font>
</span>
</div>
<br />
<el-descriptions :column="1">
<el-descriptions-item :label="$t('app.version')">
<el-select v-model="version" @change="getDetail(version)">
<el-option
v-for="(v, index) in app.versions"
:key="index"
:value="v"
:label="v"
>
{{ v }}
</el-option>
</el-select>
</el-descriptions-item>
<el-descriptions-item :label="$t('app.source')">
<el-link @click="toLink(app.source)">
<el-icon><Link /></el-icon>
</el-link>
</el-descriptions-item>
<el-descriptions-item :label="$t('app.author')">{{ app.author }}</el-descriptions-item>
</el-descriptions>
<div>
<el-button @click="openInstall" type="primary">{{ $t('app.install') }}</el-button>
</div>
</div>
<br />
<el-descriptions :column="1">
<el-descriptions-item :label="$t('app.version')">
<el-select v-model="version" @change="getDetail(version)">
<el-option v-for="(v, index) in app.versions" :key="index" :value="v" :label="v">
{{ v }}
</el-option>
</el-select>
</el-descriptions-item>
<el-descriptions-item :label="$t('app.source')">
<el-link @click="toLink(app.source)">
<el-icon><Link /></el-icon>
</el-link>
</el-descriptions-item>
<el-descriptions-item :label="$t('app.author')">{{ app.author }}</el-descriptions-item>
</el-descriptions>
<div>
<el-button @click="openInstall" type="primary">{{ $t('app.install') }}</el-button>
</div>
</div>
</el-col>
</el-row>
</div>
<el-divider border-style="double" />
<div class="detail" v-loading="loadingDetail">
<v-md-preview :text="appDetail.readme"></v-md-preview>
</div>
<Install ref="installRef"></Install>
</LayoutContent>
</el-col>
</el-row>
</div>
<el-divider border-style="double" />
<div class="detail" v-loading="loadingDetail">
<v-md-preview :text="appDetail.readme"></v-md-preview>
</div>
<Install ref="installRef"></Install>
</LayoutContent>
</el-card>
</template>
<script lang="ts" setup>

View File

@ -1,17 +1,16 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="24">
<div style="margin-bottom: 10px">
<el-check-tag :checked="activeName === 'all'" @click="routerTo('/apps/all')">
{{ $t('app.all') }}
</el-check-tag>
<el-check-tag :checked="activeName === 'installed'" @click="routerTo('/apps/installed')">
{{ $t('app.installed') }}
</el-check-tag>
</div>
</el-col>
</el-row>
<el-card class="topCard">
<el-radio-group v-model="activeName">
<el-radio-button class="topButton" size="large" @click="routerTo('/apps/all')" label="all">
{{ $t('app.all') }}
</el-radio-button>
<el-radio-button class="topButton" size="large" @click="routerTo('/apps/installed')" label="installed">
{{ $t('app.installed') }}
</el-radio-button>
</el-radio-group>
</el-card>
<br />
<LayoutContent>
<router-view></router-view>
</LayoutContent>
@ -23,6 +22,7 @@ import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const activeName = ref('all');
const routerTo = (path: string) => {
@ -43,4 +43,34 @@ onMounted(() => {
});
</script>
<style lang="scss"></style>
<style>
.topCard {
--el-card-border-color: var(--el-border-color-light);
--el-card-border-radius: 4px;
--el-card-padding: 0px;
--el-card-bg-color: var(--el-fill-color-blank);
}
.topButton .el-radio-button__inner {
display: inline-block;
line-height: 1;
white-space: nowrap;
vertical-align: middle;
background: var(--el-button-bg-color, var(--el-fill-color-blank));
border: 0;
font-weight: 350;
border-left: 0;
color: var(--el-button-text-color, var(--el-text-color-regular));
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
position: relative;
cursor: pointer;
transition: var(--el-transition-all);
-webkit-user-select: none;
user-select: none;
padding: 8px 15px;
font-size: var(--el-font-size-base);
border-radius: 0;
}
</style>

View File

@ -1,91 +1,114 @@
<template>
<div style="float: right; margin-bottom: 5px">
<el-button @click="sync">{{ $t('app.sync') }}</el-button>
</div>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" v-loading="loading">
<el-table-column :label="$t('app.name')" prop="name">
<template #default="{ row }">
{{ row.name }}
<el-tag round effect="dark" v-if="row.canUpdate">{{ $t('app.canUpdate') }}</el-tag>
<el-card>
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search" v-loading="loading">
<template #toolbar>
<el-row>
<el-col :span="18">
<el-button @click="sync">{{ $t('app.sync') }}</el-button>
</el-col>
<el-col :span="6">
<div style="float: right">
<el-input
style="display: inline; margin-right: 5px"
v-model="searchName"
clearable
@clear="search()"
></el-input>
<el-button
style="display: inline; margin-right: 5px"
v-model="searchName"
@click="search()"
>
{{ '搜索' }}
</el-button>
</div>
</el-col>
</el-row>
</template>
</el-table-column>
<!-- <el-table-column :label="$t('app.description')" prop="description"></el-table-column> -->
<el-table-column :label="$t('app.appName')" prop="app.name"></el-table-column>
<el-table-column :label="$t('app.version')" prop="version"></el-table-column>
<el-table-column :label="$t('app.backup')">
<template #default="{ row }">
<el-link :underline="false" @click="openBackups(row.id)" type="primary">
{{ $t('app.backup') }} ({{ row.backups.length }})
</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('app.status')">
<template #default="{ row }">
<el-popover
v-if="row.status === 'Error'"
placement="bottom"
:width="400"
trigger="hover"
:content="row.message"
>
<template #reference>
<el-tag type="error">{{ row.status }}</el-tag>
</template>
</el-popover>
<el-table-column :label="$t('app.name')" prop="name">
<template #default="{ row }">
{{ row.name }}
<el-tag round effect="dark" v-if="row.canUpdate">{{ $t('app.canUpdate') }}</el-tag>
</template>
</el-table-column>
<!-- <el-table-column :label="$t('app.description')" prop="description"></el-table-column> -->
<el-table-column :label="$t('app.appName')" prop="app.name"></el-table-column>
<el-table-column :label="$t('app.version')" prop="version"></el-table-column>
<el-table-column :label="$t('app.backup')">
<template #default="{ row }">
<el-link :underline="false" @click="openBackups(row.id)" type="primary">
{{ $t('app.backup') }} ({{ row.backups.length }})
</el-link>
</template>
</el-table-column>
<el-table-column :label="$t('app.status')">
<template #default="{ row }">
<el-popover
v-if="row.status === 'Error'"
placement="bottom"
:width="400"
trigger="hover"
:content="row.message"
>
<template #reference>
<el-tag type="error">{{ row.status }}</el-tag>
</template>
</el-popover>
<el-tag v-else>
<el-icon v-if="row.status === 'Installing'" class="is-loading">
<Loading />
</el-icon>
{{ row.status }}
</el-tag>
<el-tag v-else>
<el-icon v-if="row.status === 'Installing'" class="is-loading">
<Loading />
</el-icon>
{{ row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
width="300px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<el-dialog v-model="open" :title="$t('commons.msg.operate')" :before-close="handleClose" width="30%">
<div style="text-align: center">
<p>{{ $t('app.versioneSelect') }}</p>
<el-select v-model="operateReq.detailId">
<el-option
v-for="(version, index) in versions"
:key="index"
:value="version.detailId"
:label="version.version"
></el-option>
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button
type="primary"
@click="operate"
:disabled="operateReq.operate == 'update' && versions == null"
>
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
<fu-table-operations
width="300px"
:ellipsis="10"
:buttons="buttons"
:label="$t('commons.table.operate')"
fixed="right"
fix
/>
</ComplexTable>
<el-dialog v-model="open" :title="$t('commons.msg.operate')" :before-close="handleClose" width="30%">
<div style="text-align: center">
<p>{{ $t('app.versioneSelect') }}</p>
<el-select v-model="operateReq.detailId">
<el-option
v-for="(version, index) in versions"
:key="index"
:value="version.detailId"
:label="version.version"
></el-option>
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">{{ $t('commons.button.cancel') }}</el-button>
<el-button
type="primary"
@click="operate"
:disabled="operateReq.operate == 'update' && versions == null"
>
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-dialog>
<Backups ref="backupRef" @close="search"></Backups>
</el-dialog>
<Backups ref="backupRef" @close="search"></Backups>
</el-card>
</template>
<script lang="ts" setup>
import { GetAppInstalled, InstalledOp, SyncInstalledApp, GetAppUpdateVersions } from '@/api/modules/app';
import { SearchAppInstalled, InstalledOp, SyncInstalledApp, GetAppUpdateVersions } from '@/api/modules/app';
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util';
@ -110,6 +133,7 @@ let operateReq = reactive({
});
let versions = ref<App.VersionDetail[]>();
const backupRef = ref();
let searchName = ref('');
const sync = () => {
loading.value = true;
@ -127,9 +151,10 @@ const search = () => {
const req = {
page: paginationConfig.currentPage,
pageSize: paginationConfig.pageSize,
name: searchName.value,
};
GetAppInstalled(req).then((res) => {
SearchAppInstalled(req).then((res) => {
data.value = res.data.items;
paginationConfig.total = res.data.total;
});

View File

@ -114,7 +114,7 @@
<script lang="ts" setup name="CreateWebSite">
import { App } from '@/api/interface/app';
import { WebSite } from '@/api/interface/website';
import { GetApp, GetAppDetail, SearchApp, SearchAppInstalled } from '@/api/modules/app';
import { GetApp, GetAppDetail, SearchApp, GetAppInstalled } from '@/api/modules/app';
import { CreateWebsite, ListGroups } from '@/api/modules/website';
import { Rules } from '@/global/form-rules';
import i18n from '@/lang';
@ -177,7 +177,7 @@ const handleClose = () => {
};
const searchAppInstalled = () => {
SearchAppInstalled({ type: 'website' }).then((res) => {
GetAppInstalled({ type: 'website' }).then((res) => {
appInstalles.value = res.data;
if (res.data.length > 0) {
website.value.appInstallId = res.data[0].id;