mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-01-31 14:08:06 +08:00
feat: 增加应用详情页
This commit is contained in:
parent
367623293c
commit
da85e416ea
@ -5,6 +5,7 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (b *BaseApi) AppSearch(c *gin.Context) {
|
||||
@ -30,3 +31,33 @@ func (b *BaseApi) AppSync(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, "")
|
||||
}
|
||||
|
||||
func (b *BaseApi) GetApp(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
u64, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
appDTO, err := appService.GetApp(uint(u64))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, appDTO)
|
||||
}
|
||||
func (b *BaseApi) GetAppDetail(c *gin.Context) {
|
||||
idStr := c.Param("appid")
|
||||
u64, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
version := c.Param("version")
|
||||
appDetailDTO, err := appService.GetAppDetail(uint(u64), version)
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, appDetailDTO)
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ type AppRes struct {
|
||||
|
||||
type AppDTO struct {
|
||||
model.App
|
||||
Tags []model.Tag `json:"tags"`
|
||||
Versions []string `json:"versions"`
|
||||
Tags []model.Tag `json:"tags"`
|
||||
}
|
||||
|
||||
type AppDetailDTO struct {
|
||||
model.AppDetail
|
||||
}
|
||||
|
||||
type AppList struct {
|
||||
@ -53,6 +58,6 @@ type AppFormFields struct {
|
||||
|
||||
type AppRequest struct {
|
||||
PageInfo
|
||||
Name string `json:"name"`
|
||||
Types []string `json:"types"`
|
||||
Name string `json:"name"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package model
|
||||
import "time"
|
||||
|
||||
type BaseModel struct {
|
||||
ID uint `gorm:"primarykey;AUTO_INCREMENT"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
@ -11,12 +11,6 @@ import (
|
||||
type AppRepo struct {
|
||||
}
|
||||
|
||||
func (a AppRepo) WithInTypes(types []string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("type in (?)", types)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) {
|
||||
var apps []model.App
|
||||
db := global.DB.Model(&model.App{})
|
||||
@ -29,6 +23,18 @@ func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, err
|
||||
return count, apps, err
|
||||
}
|
||||
|
||||
func (a AppRepo) GetFirst(opts ...DBOption) (model.App, error) {
|
||||
var app model.App
|
||||
db := global.DB.Model(&model.App{})
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
if err := db.First(&app).Error; err != nil {
|
||||
return app, err
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (a AppRepo) BatchCreate(ctx context.Context, apps []*model.App) error {
|
||||
db := ctx.Value("db").(*gorm.DB)
|
||||
return db.Omit(clause.Associations).Create(apps).Error
|
||||
|
@ -3,12 +3,34 @@ package repo
|
||||
import (
|
||||
"context"
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AppDetailRepo struct {
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) WithVersion(version string) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("version = ?", version)
|
||||
}
|
||||
}
|
||||
func (a AppDetailRepo) WithAppId(id uint) DBOption {
|
||||
return func(g *gorm.DB) *gorm.DB {
|
||||
return g.Where("app_id = ?", id)
|
||||
}
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) GetAppDetail(opts ...DBOption) (model.AppDetail, error) {
|
||||
var detail model.AppDetail
|
||||
db := global.DB
|
||||
for _, opt := range opts {
|
||||
db = opt(db)
|
||||
}
|
||||
err := db.Find(&detail).Error
|
||||
return detail, err
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) BatchCreate(ctx context.Context, details []*model.AppDetail) error {
|
||||
db := ctx.Value("db").(*gorm.DB)
|
||||
return db.Model(&model.AppDetail{}).Create(&details).Error
|
||||
@ -19,7 +41,7 @@ func (a AppDetailRepo) DeleteByAppIds(ctx context.Context, appIds []uint) error
|
||||
return db.Where("app_id in (?)", appIds).Delete(&model.AppDetail{}).Error
|
||||
}
|
||||
|
||||
func (a AppDetailRepo) GetByAppId(ctx context.Context, appId string) ([]model.AppDetail, error) {
|
||||
func (a AppDetailRepo) GetByAppId(ctx context.Context, appId uint) ([]model.AppDetail, error) {
|
||||
db := ctx.Value("db").(*gorm.DB)
|
||||
var details []model.AppDetail
|
||||
if err := db.Where("app_id = ?", appId).Find(&details).Error; err != nil {
|
||||
|
@ -27,3 +27,11 @@ func (a AppTagRepo) GetByAppId(appId uint) ([]model.AppTag, error) {
|
||||
}
|
||||
return appTags, nil
|
||||
}
|
||||
|
||||
func (a AppTagRepo) GetByTagIds(tagIds []uint) ([]model.AppTag, error) {
|
||||
var appTags []model.AppTag
|
||||
if err := global.DB.Where("tag_id in (?)", tagIds).Find(&appTags).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appTags, nil
|
||||
}
|
||||
|
@ -35,3 +35,11 @@ func (t TagRepo) GetByIds(ids []uint) ([]model.Tag, error) {
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func (t TagRepo) GetByKeys(keys []string) ([]model.Tag, error) {
|
||||
var tags []model.Tag
|
||||
if err := global.DB.Where("key in (?)", keys).Find(&tags).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
@ -7,10 +7,12 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/app/repo"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/utils/common"
|
||||
"golang.org/x/net/context"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type AppService struct {
|
||||
@ -23,8 +25,25 @@ func (a AppService) Page(req dto.AppRequest) (interface{}, error) {
|
||||
if req.Name != "" {
|
||||
opts = append(opts, commonRepo.WithLikeName(req.Name))
|
||||
}
|
||||
if len(req.Types) != 0 {
|
||||
opts = append(opts, appRepo.WithInTypes(req.Types))
|
||||
if len(req.Tags) != 0 {
|
||||
tags, err := tagRepo.GetByKeys(req.Tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tagIds []uint
|
||||
for _, t := range tags {
|
||||
tagIds = append(tagIds, t.ID)
|
||||
}
|
||||
appTags, err := appTagRepo.GetByTagIds(tagIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var appIds []uint
|
||||
for _, t := range appTags {
|
||||
appIds = append(appIds, t.AppId)
|
||||
}
|
||||
|
||||
opts = append(opts, commonRepo.WithIdsIn(appIds))
|
||||
}
|
||||
var res dto.AppRes
|
||||
total, apps, err := appRepo.Page(req.Page, req.PageSize, opts...)
|
||||
@ -62,6 +81,47 @@ func (a AppService) Page(req dto.AppRequest) (interface{}, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetApp(id uint) (dto.AppDTO, error) {
|
||||
var appDTO dto.AppDTO
|
||||
app, err := appRepo.GetFirst(commonRepo.WithByID(id))
|
||||
if err != nil {
|
||||
return appDTO, err
|
||||
}
|
||||
appDTO.App = app
|
||||
details, err := appDetailRepo.GetByAppId(context.WithValue(context.Background(), "db", global.DB), id)
|
||||
if err != nil {
|
||||
return appDTO, err
|
||||
}
|
||||
var versionsRaw []string
|
||||
for _, detail := range details {
|
||||
versionsRaw = append(versionsRaw, detail.Version)
|
||||
}
|
||||
|
||||
sort.Slice(versionsRaw, func(i, j int) bool {
|
||||
return common.CompareVersion(versionsRaw[i], versionsRaw[j])
|
||||
})
|
||||
appDTO.Versions = versionsRaw
|
||||
|
||||
return appDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppDetail(appId uint, version string) (dto.AppDetailDTO, error) {
|
||||
|
||||
var (
|
||||
appDetailDTO dto.AppDetailDTO
|
||||
)
|
||||
|
||||
var opts []repo.DBOption
|
||||
opts = append(opts, appDetailRepo.WithAppId(appId), appDetailRepo.WithVersion(version))
|
||||
detail, err := appDetailRepo.GetAppDetail(opts...)
|
||||
if err != nil {
|
||||
return appDetailDTO, err
|
||||
}
|
||||
|
||||
appDetailDTO.AppDetail = detail
|
||||
return appDetailDTO, nil
|
||||
}
|
||||
|
||||
func (a AppService) Sync() error {
|
||||
//TODO 从 oss 拉取最新列表
|
||||
var appConfig model.AppConfig
|
||||
|
@ -76,6 +76,7 @@ require (
|
||||
github.com/google/flatbuffers v1.12.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
|
@ -247,6 +247,8 @@ github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q
|
||||
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
|
||||
github.com/gwatts/gin-adapter v1.0.0 h1:TsmmhYTR79/RMTsfYJ2IQvI1F5KZ3ZFJxuQSYEOpyIA=
|
||||
github.com/gwatts/gin-adapter v1.0.0/go.mod h1:44AEV+938HsS0mjfXtBDCUZS9vONlF2gwvh8wu4sRYc=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
|
@ -17,5 +17,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
|
||||
{
|
||||
appRouter.POST("/sync", baseApi.AppSync)
|
||||
appRouter.POST("/search", baseApi.AppSearch)
|
||||
appRouter.GET("/:id", baseApi.GetApp)
|
||||
appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail)
|
||||
}
|
||||
}
|
||||
|
40
backend/utils/common/common.go
Normal file
40
backend/utils/common/common.go
Normal file
@ -0,0 +1,40 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CompareVersion(version1 string, version2 string) bool {
|
||||
version1s := strings.Split(version1, ".")
|
||||
version2s := strings.Split(version2, ".")
|
||||
|
||||
n := min(len(version1s), len(version2s))
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
for i := 0; i < n; i++ {
|
||||
sVersion1s := re.FindAllString(version1s[i], -1)
|
||||
sVersion2s := re.FindAllString(version2s[i], -1)
|
||||
if len(sVersion1s) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(sVersion2s) == 0 {
|
||||
return false
|
||||
}
|
||||
v1num, _ := strconv.Atoi(sVersion1s[0])
|
||||
v2num, _ := strconv.Atoi(sVersion2s[0])
|
||||
if v1num == v2num {
|
||||
continue
|
||||
} else {
|
||||
return v1num > v2num
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { ReqPage } from '.';
|
||||
import { ReqPage, CommonModel } from '.';
|
||||
|
||||
export namespace App {
|
||||
export interface App {
|
||||
export interface App extends CommonModel {
|
||||
name: string;
|
||||
icon: string;
|
||||
key: string;
|
||||
@ -12,6 +12,10 @@ export namespace App {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface AppDTO extends App {
|
||||
versions: string[];
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
key: string;
|
||||
name: string;
|
||||
@ -25,18 +29,17 @@ export namespace App {
|
||||
tags: App.Tag[];
|
||||
}
|
||||
|
||||
export interface AppDetail {
|
||||
name: string;
|
||||
export interface AppDetail extends CommonModel {
|
||||
appId: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
sourceLink: string;
|
||||
versions: string[];
|
||||
version: string;
|
||||
readme: string;
|
||||
athor: string;
|
||||
formFields: string;
|
||||
dockerCompose: string;
|
||||
}
|
||||
|
||||
export interface AppReq extends ReqPage {
|
||||
name: string;
|
||||
types: string[];
|
||||
tags: string[];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
// export const GetAppList = ()
|
||||
import http from '@/api';
|
||||
import { App } from '../interface/app';
|
||||
|
||||
@ -9,3 +8,11 @@ export const SyncApp = () => {
|
||||
export const SearchApp = (req: App.AppReq) => {
|
||||
return http.post<App.AppResPage>('apps/search', req);
|
||||
};
|
||||
|
||||
export const GetApp = (id: number) => {
|
||||
return http.get<App.AppDTO>('apps/' + id);
|
||||
};
|
||||
|
||||
export const GetAppDetail = (id: number, version: string) => {
|
||||
return http.get<App.AppDetail>('apps/detail/' + id + '/' + version);
|
||||
};
|
||||
|
@ -392,5 +392,7 @@ export default {
|
||||
installed: '已安装',
|
||||
all: '全部',
|
||||
version: '版本',
|
||||
detail: '详情',
|
||||
install: '安装',
|
||||
},
|
||||
};
|
||||
|
@ -16,17 +16,8 @@ const appStoreRouter = {
|
||||
component: () => import('@/views/app-store/index.vue'),
|
||||
meta: {},
|
||||
},
|
||||
// {
|
||||
// path: '/apps/detail/:name',
|
||||
// name: 'AppDetail',
|
||||
// component: () => import('@/views/app-store/detail/index.vue'),
|
||||
// meta: {
|
||||
// hidden: true,
|
||||
// title: 'menu.apps',
|
||||
// },
|
||||
// },
|
||||
{
|
||||
path: '/apps/detail/:name',
|
||||
path: '/apps/detail/:id',
|
||||
name: 'AppDetail',
|
||||
props: true,
|
||||
hidden: true,
|
||||
|
@ -1,29 +0,0 @@
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"name": "Mysql",
|
||||
"icon": "mysql.png",
|
||||
"description": "常用的关系型数据库",
|
||||
"tags": ["数据库"]
|
||||
},
|
||||
{
|
||||
"name": "Redis",
|
||||
"icon": "redis.png",
|
||||
"description": "缓存数据库",
|
||||
"tags": ["数据库"]
|
||||
},
|
||||
{
|
||||
"name": "Wordpress",
|
||||
"icon": "wordpress.png",
|
||||
"description": "老牌博客平台",
|
||||
"tags": ["网站"]
|
||||
},
|
||||
{
|
||||
"name": "Halo",
|
||||
"icon": "halo.png",
|
||||
"description": "现代化的博客平台",
|
||||
"tags": ["网站"]
|
||||
}
|
||||
],
|
||||
"tags": ["数据库", "网站", "测试", "开发"]
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "Halo",
|
||||
"description": "更好用的博客模版",
|
||||
"versions": ["0.0.1", "0.0.2"],
|
||||
"sourceLink": "",
|
||||
"athor": "halo",
|
||||
"status": "",
|
||||
"readme": "",
|
||||
"icon": "halo.png"
|
||||
}
|
@ -1,138 +1,96 @@
|
||||
<template>
|
||||
<LayoutContent>
|
||||
<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="getImageUrl(appDetail.icon)"></el-image>
|
||||
<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">{{ appDetail.name }}</font>
|
||||
<font size="5" style="font-weight: 800">{{ app.name }}</font>
|
||||
</div>
|
||||
<div class="a-description">
|
||||
<span>
|
||||
<font>
|
||||
{{ appDetail.description }}
|
||||
{{ app.shortDesc }}
|
||||
</font>
|
||||
</span>
|
||||
</div>
|
||||
<br />
|
||||
<el-descriptions :column="1">
|
||||
<el-descriptions-item :label="$t('app.version')">
|
||||
<el-select v-model="appSelect.version">
|
||||
<el-option
|
||||
v-for="(v, index) in appDetail.versions"
|
||||
:key="index"
|
||||
:value="v"
|
||||
:label="v"
|
||||
>
|
||||
<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="'链接'">
|
||||
<el-link>source</el-link>
|
||||
<el-descriptions-item :label="'来源'">
|
||||
<el-link @click="toLink(app.source)">
|
||||
<el-icon><Link /></el-icon>
|
||||
</el-link>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :label="'来源'">FIT2CLOUD</el-descriptions-item>
|
||||
<el-descriptions-item :label="'作者'">{{ app.author }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div>
|
||||
<el-button type="primary">安装</el-button>
|
||||
<el-button type="primary">{{ $t('app.install') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-divider border-style="double" />
|
||||
<div class="detail">
|
||||
<v-md-preview :text="readme"></v-md-preview>
|
||||
<div class="detail" v-loading="loadingDetail">
|
||||
<v-md-preview :text="appDetail.readme"></v-md-preview>
|
||||
</div>
|
||||
</LayoutContent>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { GetApp, GetAppDetail } from '@/api/modules/app';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { getAssetsFile } from '@/utils/image';
|
||||
import { reactive, ref } from 'vue';
|
||||
import detail from './detail.json';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
let appDetail = ref<any>();
|
||||
appDetail.value = detail;
|
||||
|
||||
let appSelect = reactive({
|
||||
version: '',
|
||||
interface OperateProps {
|
||||
id: number;
|
||||
}
|
||||
const props = withDefaults(defineProps<OperateProps>(), {
|
||||
id: 0,
|
||||
});
|
||||
appSelect.version = appDetail.value.versions[0];
|
||||
let app = ref<any>({});
|
||||
let appDetail = ref<any>({});
|
||||
let version = ref('');
|
||||
let loadingDetail = ref(false);
|
||||
|
||||
let readme = ref<string>(`<p align="center">
|
||||
<a href="https://halo.run" target="_blank" rel="noopener noreferrer">
|
||||
<img width="100" src="https://halo.run/logo" alt="Halo logo" />
|
||||
</a>
|
||||
</p>
|
||||
<p align="center"><b>Halo</b> [ˈheɪloʊ],一款现代化的开源博客/CMS系统,值得一试。</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/halo-dev/halo/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/halo-dev/halo.svg?style=flat-square" /></a>
|
||||
<a href="https://github.com/halo-dev/halo/releases"><img alt="GitHub All Releases" src="https://img.shields.io/github/downloads/halo-dev/halo/total.svg?style=flat-square" /></a>
|
||||
<a href="https://hub.docker.com/r/halohub/halo"><img alt="Docker pulls" src="https://img.shields.io/docker/pulls/halohub/halo?style=flat-square" /></a>
|
||||
<a href="https://github.com/halo-dev/halo/commits"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/halo-dev/halo.svg?style=flat-square" /></a>
|
||||
<a href="https://github.com/halo-dev/halo/actions"><img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/halo-dev/halo/Halo%20CI?style=flat-square" /></a>
|
||||
<br />
|
||||
<a href="https://halo.run">官网</a>
|
||||
<a href="https://docs.halo.run">文档</a>
|
||||
<a href="https://bbs.halo.run">社区</a>
|
||||
<a href="https://gitee.com/halo-dev">Gitee</a>
|
||||
<a href="https://t.me/halo_dev">Telegram 频道</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
详细部署文档请查阅:<https://docs.halo.run>
|
||||
|
||||
## 在线体验
|
||||
|
||||
- 环境地址:<https://demo.halo.run>
|
||||
- 后台地址:<https://demo.halo.run/admin>
|
||||
- 用户名:demo
|
||||
- 密码:P@ssw0rd123..
|
||||
- 使用前请阅读:<https://demo.halo.run/archives/tips>
|
||||
|
||||
## 生态
|
||||
|
||||
| 项目 | 状态 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| [halo-admin](https://github.com/halo-dev/halo-admin) | <a href="https://github.com/halo-dev/halo-admin/releases"><img alt="GitHub release" src="https://img.shields.io/github/release/halo-dev/halo-admin.svg?style=flat-square" /></a> | Web 管理端 UI,已内置在主应用 |
|
||||
| [js-sdk](https://github.com/halo-dev/js-sdk) | <a href="https://github.com/halo-dev/js-sdk"><img alt="npm release" src="https://img.shields.io/npm/v/@halo-dev/content-api?style=flat-square"/></a> | JavaScript SDK |
|
||||
| [halo-comment](https://github.com/halo-dev/halo-comment) | <a href="https://www.npmjs.com/package/halo-comment"><img alt="npm release" src="https://img.shields.io/npm/v/halo-comment?style=flat-square"/></a> | 独立评论组件,可以非常方便的集成到主题中 |
|
||||
| [halo-comment-normal](https://github.com/halo-dev/halo-comment-normal) | <a href="https://www.npmjs.com/package/halo-comment-normal"><img alt="npm release" src="https://img.shields.io/npm/v/halo-comment-normal?style=flat-square"/></a> | 另外一款评论组件 |
|
||||
| [halo-mobile-app](https://github.com/halo-dev/halo-mobile-app) | 已停止维护 | 移动端管理 APP |
|
||||
| [tencent-cloudbase-halo](https://github.com/halo-dev/tencent-cloudbase-halo) | 无 | 腾讯云 CloudBase 一键部署配置 |
|
||||
| [halo-theme-\*](https://github.com/topics/halo-theme) | 无 | GitHub 上开源的 Halo 主题集合 |
|
||||
|
||||
## 许可证
|
||||
|
||||
[![license](https://img.shields.io/github/license/halo-dev/halo.svg?style=flat-square)](https://github.com/halo-dev/halo/blob/master/LICENSE)
|
||||
|
||||
Halo 使用 GPL-v3.0 协议开源,请遵守开源协议。
|
||||
|
||||
## 贡献
|
||||
|
||||
参考 [CONTRIBUTING](https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md)。
|
||||
|
||||
<a href="https://github.com/halo-dev/halo/graphs/contributors"><img src="https://opencollective.com/halo/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
## 状态
|
||||
|
||||
![Repobeats analytics](https://repobeats.axiom.co/api/embed/ad008b2151c22e7cf734d2688befaa795d593b95.svg 'Repobeats analytics image')
|
||||
|
||||
`);
|
||||
|
||||
const getImageUrl = (name: string) => {
|
||||
return getAssetsFile(name);
|
||||
const getApp = () => {
|
||||
GetApp(props.id).then((res) => {
|
||||
app.value = res.data;
|
||||
version.value = app.value.versions[0];
|
||||
getDetail(version.value);
|
||||
});
|
||||
};
|
||||
|
||||
const getDetail = (version: string) => {
|
||||
loadingDetail.value = true;
|
||||
GetAppDetail(props.id, version)
|
||||
.then((res) => {
|
||||
appDetail.value = res.data;
|
||||
})
|
||||
.finally(() => {
|
||||
loadingDetail.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const toLink = (link: string) => {
|
||||
window.open(link, '_blank');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getApp();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -1,31 +1,19 @@
|
||||
<template>
|
||||
<LayoutContent>
|
||||
<el-row :gutter="20">
|
||||
<!-- <el-col :span="24">
|
||||
<div class="header">
|
||||
<el-radio-group v-model="activeName">
|
||||
<el-radio-button label="all">
|
||||
{{ $t('app.all') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button label="installed">
|
||||
{{ $t('app.installed') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-col> -->
|
||||
<el-col :span="12">
|
||||
<el-input></el-input>
|
||||
<el-input v-model="req.name" @blur="searchByName"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-select v-model="selectTags" multiple style="width: 100%">
|
||||
<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-button @click="sync">同步</el-button>
|
||||
<!-- <el-button @click="sync">同步</el-button> -->
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col v-for="(app, index) in apps" :key="index" :xs="8" :sm="8" :lg="4">
|
||||
<div @click="getAppDetail(app.name)">
|
||||
<div @click="getAppDetail(app.id)">
|
||||
<el-card :body-style="{ padding: '0px' }" class="a-card">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8">
|
||||
@ -63,32 +51,31 @@
|
||||
<script lang="ts" setup>
|
||||
import { App } from '@/api/interface/app';
|
||||
import LayoutContent from '@/layout/layout-content.vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import router from '@/routers';
|
||||
import { SyncApp } from '@/api/modules/app';
|
||||
// import { SyncApp } from '@/api/modules/app';
|
||||
import { SearchApp } from '@/api/modules/app';
|
||||
|
||||
let req = ref<App.AppReq>({
|
||||
let req = reactive({
|
||||
name: '',
|
||||
types: [],
|
||||
tags: [],
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
});
|
||||
|
||||
let apps = ref<App.App[]>([]);
|
||||
let tags = ref<App.Tag[]>([]);
|
||||
let selectTags = ref<string[]>([]);
|
||||
const colorArr = ['#6495ED', '#54FF9F', '#BEBEBE', '#FFF68F', '#FFFF00', '#8B0000'];
|
||||
|
||||
const getColor = (index: number) => {
|
||||
return colorArr[index];
|
||||
};
|
||||
|
||||
const sync = () => {
|
||||
SyncApp().then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
};
|
||||
// const sync = () => {
|
||||
// SyncApp().then((res) => {
|
||||
// console.log(res);
|
||||
// });
|
||||
// };
|
||||
|
||||
const search = async (req: App.AppReq) => {
|
||||
await SearchApp(req).then((res) => {
|
||||
@ -97,15 +84,24 @@ const search = async (req: App.AppReq) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getAppDetail = (name: string) => {
|
||||
const getAppDetail = (id: number) => {
|
||||
console.log(id);
|
||||
let params: { [key: string]: any } = {
|
||||
name: name,
|
||||
id: id,
|
||||
};
|
||||
router.push({ name: 'AppDetail', params });
|
||||
};
|
||||
|
||||
const changeTag = () => {
|
||||
search(req);
|
||||
};
|
||||
|
||||
const searchByName = () => {
|
||||
search(req);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
search(req.value);
|
||||
search(req);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
Loading…
x
Reference in New Issue
Block a user