1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-02-07 17:10:07 +08:00

feat: 增加应用详情页

This commit is contained in:
zhengkunwang223 2022-09-23 16:33:55 +08:00 committed by zhengkunwang223
parent 367623293c
commit da85e416ea
21 changed files with 299 additions and 196 deletions

View File

@ -5,6 +5,7 @@ import (
"github.com/1Panel-dev/1Panel/app/dto" "github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/constant" "github.com/1Panel-dev/1Panel/constant"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"strconv"
) )
func (b *BaseApi) AppSearch(c *gin.Context) { func (b *BaseApi) AppSearch(c *gin.Context) {
@ -30,3 +31,33 @@ func (b *BaseApi) AppSync(c *gin.Context) {
} }
helper.SuccessWithData(c, "") 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)
}

View File

@ -12,7 +12,12 @@ type AppRes struct {
type AppDTO struct { type AppDTO struct {
model.App model.App
Tags []model.Tag `json:"tags"` Versions []string `json:"versions"`
Tags []model.Tag `json:"tags"`
}
type AppDetailDTO struct {
model.AppDetail
} }
type AppList struct { type AppList struct {
@ -53,6 +58,6 @@ type AppFormFields struct {
type AppRequest struct { type AppRequest struct {
PageInfo PageInfo
Name string `json:"name"` Name string `json:"name"`
Types []string `json:"types"` Tags []string `json:"tags"`
} }

View File

@ -3,7 +3,7 @@ package model
import "time" import "time"
type BaseModel struct { type BaseModel struct {
ID uint `gorm:"primarykey;AUTO_INCREMENT"` ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"`
CreatedAt time.Time CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time UpdatedAt time.Time `json:"updatedAt"`
} }

View File

@ -11,12 +11,6 @@ import (
type AppRepo struct { 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) { func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) {
var apps []model.App var apps []model.App
db := global.DB.Model(&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 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 { func (a AppRepo) BatchCreate(ctx context.Context, apps []*model.App) error {
db := ctx.Value("db").(*gorm.DB) db := ctx.Value("db").(*gorm.DB)
return db.Omit(clause.Associations).Create(apps).Error return db.Omit(clause.Associations).Create(apps).Error

View File

@ -3,12 +3,34 @@ package repo
import ( import (
"context" "context"
"github.com/1Panel-dev/1Panel/app/model" "github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/global"
"gorm.io/gorm" "gorm.io/gorm"
) )
type AppDetailRepo struct { 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 { func (a AppDetailRepo) BatchCreate(ctx context.Context, details []*model.AppDetail) error {
db := ctx.Value("db").(*gorm.DB) db := ctx.Value("db").(*gorm.DB)
return db.Model(&model.AppDetail{}).Create(&details).Error 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 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) db := ctx.Value("db").(*gorm.DB)
var details []model.AppDetail var details []model.AppDetail
if err := db.Where("app_id = ?", appId).Find(&details).Error; err != nil { if err := db.Where("app_id = ?", appId).Find(&details).Error; err != nil {

View File

@ -27,3 +27,11 @@ func (a AppTagRepo) GetByAppId(appId uint) ([]model.AppTag, error) {
} }
return appTags, nil 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
}

View File

@ -35,3 +35,11 @@ func (t TagRepo) GetByIds(ids []uint) ([]model.Tag, error) {
} }
return tags, nil 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
}

View File

@ -7,10 +7,12 @@ import (
"github.com/1Panel-dev/1Panel/app/model" "github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/app/repo" "github.com/1Panel-dev/1Panel/app/repo"
"github.com/1Panel-dev/1Panel/global" "github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/utils/common"
"golang.org/x/net/context" "golang.org/x/net/context"
"os" "os"
"path" "path"
"reflect" "reflect"
"sort"
) )
type AppService struct { type AppService struct {
@ -23,8 +25,25 @@ func (a AppService) Page(req dto.AppRequest) (interface{}, error) {
if req.Name != "" { if req.Name != "" {
opts = append(opts, commonRepo.WithLikeName(req.Name)) opts = append(opts, commonRepo.WithLikeName(req.Name))
} }
if len(req.Types) != 0 { if len(req.Tags) != 0 {
opts = append(opts, appRepo.WithInTypes(req.Types)) 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 var res dto.AppRes
total, apps, err := appRepo.Page(req.Page, req.PageSize, opts...) 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 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 { func (a AppService) Sync() error {
//TODO 从 oss 拉取最新列表 //TODO 从 oss 拉取最新列表
var appConfig model.AppConfig var appConfig model.AppConfig

View File

@ -76,6 +76,7 @@ require (
github.com/google/flatbuffers v1.12.1 // indirect github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/securecookie v1.1.1 // 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/hashicorp/hcl v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect

View File

@ -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/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 h1:TsmmhYTR79/RMTsfYJ2IQvI1F5KZ3ZFJxuQSYEOpyIA=
github.com/gwatts/gin-adapter v1.0.0/go.mod h1:44AEV+938HsS0mjfXtBDCUZS9vONlF2gwvh8wu4sRYc= 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.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/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=

View File

@ -17,5 +17,7 @@ func (a *AppRouter) InitAppRouter(Router *gin.RouterGroup) {
{ {
appRouter.POST("/sync", baseApi.AppSync) appRouter.POST("/sync", baseApi.AppSync)
appRouter.POST("/search", baseApi.AppSearch) appRouter.POST("/search", baseApi.AppSearch)
appRouter.GET("/:id", baseApi.GetApp)
appRouter.GET("/detail/:appid/:version", baseApi.GetAppDetail)
} }
} }

View 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
}

View File

@ -1,7 +1,7 @@
import { ReqPage } from '.'; import { ReqPage, CommonModel } from '.';
export namespace App { export namespace App {
export interface App { export interface App extends CommonModel {
name: string; name: string;
icon: string; icon: string;
key: string; key: string;
@ -12,6 +12,10 @@ export namespace App {
type: string; type: string;
} }
export interface AppDTO extends App {
versions: string[];
}
export interface Tag { export interface Tag {
key: string; key: string;
name: string; name: string;
@ -25,18 +29,17 @@ export namespace App {
tags: App.Tag[]; tags: App.Tag[];
} }
export interface AppDetail { export interface AppDetail extends CommonModel {
name: string; appId: string;
icon: string; icon: string;
description: string; version: string;
sourceLink: string;
versions: string[];
readme: string; readme: string;
athor: string; formFields: string;
dockerCompose: string;
} }
export interface AppReq extends ReqPage { export interface AppReq extends ReqPage {
name: string; name: string;
types: string[]; tags: string[];
} }
} }

View File

@ -1,4 +1,3 @@
// export const GetAppList = ()
import http from '@/api'; import http from '@/api';
import { App } from '../interface/app'; import { App } from '../interface/app';
@ -9,3 +8,11 @@ export const SyncApp = () => {
export const SearchApp = (req: App.AppReq) => { export const SearchApp = (req: App.AppReq) => {
return http.post<App.AppResPage>('apps/search', req); 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);
};

View File

@ -392,5 +392,7 @@ export default {
installed: '已安装', installed: '已安装',
all: '全部', all: '全部',
version: '版本', version: '版本',
detail: '详情',
install: '安装',
}, },
}; };

View File

@ -16,17 +16,8 @@ const appStoreRouter = {
component: () => import('@/views/app-store/index.vue'), component: () => import('@/views/app-store/index.vue'),
meta: {}, 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', name: 'AppDetail',
props: true, props: true,
hidden: true, hidden: true,

View File

@ -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": ["数据库", "网站", "测试", "开发"]
}

View File

@ -1,10 +0,0 @@
{
"name": "Halo",
"description": "更好用的博客模版",
"versions": ["0.0.1", "0.0.2"],
"sourceLink": "",
"athor": "halo",
"status": "",
"readme": "",
"icon": "halo.png"
}

View File

@ -1,138 +1,96 @@
<template> <template>
<LayoutContent> <LayoutContent :header="$t('app.detail')" :back-name="'App'">
<div class="brief"> <div class="brief">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="4"> <el-col :span="4">
<div class="icon"> <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> </div>
</el-col> </el-col>
<el-col :span="20"> <el-col :span="20">
<div class="a-detail"> <div class="a-detail">
<div class="a-name"> <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>
<div class="a-description"> <div class="a-description">
<span> <span>
<font> <font>
{{ appDetail.description }} {{ app.shortDesc }}
</font> </font>
</span> </span>
</div> </div>
<br />
<el-descriptions :column="1"> <el-descriptions :column="1">
<el-descriptions-item :label="$t('app.version')"> <el-descriptions-item :label="$t('app.version')">
<el-select v-model="appSelect.version"> <el-select v-model="version" @change="getDetail(version)">
<el-option <el-option v-for="(v, index) in app.versions" :key="index" :value="v" :label="v">
v-for="(v, index) in appDetail.versions"
:key="index"
:value="v"
:label="v"
>
{{ v }} {{ v }}
</el-option> </el-option>
</el-select> </el-select>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="'链接'"> <el-descriptions-item :label="'来源'">
<el-link>source</el-link> <el-link @click="toLink(app.source)">
<el-icon><Link /></el-icon>
</el-link>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="'来源'">FIT2CLOUD</el-descriptions-item> <el-descriptions-item :label="'作者'">{{ app.author }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div> <div>
<el-button type="primary">安装</el-button> <el-button type="primary">{{ $t('app.install') }}</el-button>
</div> </div>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
<el-divider border-style="double" /> <el-divider border-style="double" />
<div class="detail"> <div class="detail" v-loading="loadingDetail">
<v-md-preview :text="readme"></v-md-preview> <v-md-preview :text="appDetail.readme"></v-md-preview>
</div> </div>
</LayoutContent> </LayoutContent>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { GetApp, GetAppDetail } from '@/api/modules/app';
import LayoutContent from '@/layout/layout-content.vue'; import LayoutContent from '@/layout/layout-content.vue';
import { getAssetsFile } from '@/utils/image'; import { onMounted, ref } from 'vue';
import { reactive, ref } from 'vue';
import detail from './detail.json';
let appDetail = ref<any>(); interface OperateProps {
appDetail.value = detail; id: number;
}
let appSelect = reactive({ const props = withDefaults(defineProps<OperateProps>(), {
version: '', 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"> const getApp = () => {
<a href="https://halo.run" target="_blank" rel="noopener noreferrer"> GetApp(props.id).then((res) => {
<img width="100" src="https://halo.run/logo" alt="Halo logo" /> app.value = res.data;
</a> version.value = app.value.versions[0];
</p> getDetail(version.value);
<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 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> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,31 +1,19 @@
<template> <template>
<LayoutContent> <LayoutContent>
<el-row :gutter="20"> <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-col :span="12">
<el-input></el-input> <el-input v-model="req.name" @blur="searchByName"></el-input>
</el-col> </el-col>
<el-col :span="12"> <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-option v-for="item in tags" :key="item.key" :label="item.name" :value="item.key"></el-option>
</el-select> </el-select>
</el-col> </el-col>
<el-button @click="sync">同步</el-button> <!-- <el-button @click="sync">同步</el-button> -->
</el-row> </el-row>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col v-for="(app, index) in apps" :key="index" :xs="8" :sm="8" :lg="4"> <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-card :body-style="{ padding: '0px' }" class="a-card">
<el-row :gutter="24"> <el-row :gutter="24">
<el-col :span="8"> <el-col :span="8">
@ -63,32 +51,31 @@
<script lang="ts" setup> <script lang="ts" setup>
import { App } from '@/api/interface/app'; import { App } from '@/api/interface/app';
import LayoutContent from '@/layout/layout-content.vue'; import LayoutContent from '@/layout/layout-content.vue';
import { onMounted, ref } from 'vue'; import { onMounted, reactive, ref } from 'vue';
import router from '@/routers'; import router from '@/routers';
import { SyncApp } from '@/api/modules/app'; // import { SyncApp } from '@/api/modules/app';
import { SearchApp } from '@/api/modules/app'; import { SearchApp } from '@/api/modules/app';
let req = ref<App.AppReq>({ let req = reactive({
name: '', name: '',
types: [], tags: [],
page: 1, page: 1,
pageSize: 50, pageSize: 50,
}); });
let apps = ref<App.App[]>([]); let apps = ref<App.App[]>([]);
let tags = ref<App.Tag[]>([]); let tags = ref<App.Tag[]>([]);
let selectTags = ref<string[]>([]);
const colorArr = ['#6495ED', '#54FF9F', '#BEBEBE', '#FFF68F', '#FFFF00', '#8B0000']; const colorArr = ['#6495ED', '#54FF9F', '#BEBEBE', '#FFF68F', '#FFFF00', '#8B0000'];
const getColor = (index: number) => { const getColor = (index: number) => {
return colorArr[index]; return colorArr[index];
}; };
const sync = () => { // const sync = () => {
SyncApp().then((res) => { // SyncApp().then((res) => {
console.log(res); // console.log(res);
}); // });
}; // };
const search = async (req: App.AppReq) => { const search = async (req: App.AppReq) => {
await SearchApp(req).then((res) => { 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 } = { let params: { [key: string]: any } = {
name: name, id: id,
}; };
router.push({ name: 'AppDetail', params }); router.push({ name: 'AppDetail', params });
}; };
const changeTag = () => {
search(req);
};
const searchByName = () => {
search(req);
};
onMounted(() => { onMounted(() => {
search(req.value); search(req);
}); });
</script> </script>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB