Browse Source

Merge branch 'V20221214' into zouap_static

pull/3394/head
zouap 2 years ago
parent
commit
d324b8b914
54 changed files with 3467 additions and 100 deletions
  1. +21
    -1
      models/cloudbrain.go
  2. +20
    -0
      models/list_options.go
  3. +229
    -4
      models/repo.go
  4. +33
    -21
      models/repo_list.go
  5. +20
    -0
      models/repo_statistic.go
  6. +31
    -0
      models/repo_tag.go
  7. +14
    -0
      models/topic.go
  8. +34
    -0
      models/user.go
  9. +6
    -0
      models/user_business_analysis.go
  10. +9
    -0
      modules/redis/redis_key/repo_redis_key.go
  11. +8
    -0
      modules/setting/setting.go
  12. +2
    -1
      package.json
  13. +3
    -11
      public/home/home.js
  14. +34
    -0
      routers/admin/resources.go
  15. +14
    -2
      routers/api/v1/repo/topic.go
  16. +106
    -0
      routers/home.go
  17. +3
    -0
      routers/private/internal.go
  18. +23
    -4
      routers/repo/ai_model_manage.go
  19. +3
    -0
      routers/repo/attachment.go
  20. +7
    -1
      routers/repo/cloudbrain_statistic.go
  21. +2
    -0
      routers/repo/repo_statistic.go
  22. +12
    -1
      routers/routes/routes.go
  23. +88
    -0
      services/repository/contributor.go
  24. +51
    -6
      services/repository/repository.go
  25. +315
    -0
      services/repository/square.go
  26. +5
    -5
      templates/base/head_navbar.tmpl
  27. +5
    -5
      templates/base/head_navbar_fluid.tmpl
  28. +5
    -5
      templates/base/head_navbar_home.tmpl
  29. +4
    -4
      templates/base/head_navbar_pro.tmpl
  30. +2
    -2
      templates/explore/navbar.tmpl
  31. +8
    -0
      templates/explore/repos/search.tmpl
  32. +16
    -0
      templates/explore/repos/square.tmpl
  33. +1
    -1
      templates/user/dashboard/dashboard.tmpl
  34. +2
    -2
      web_src/js/index.js
  35. +12
    -0
      web_src/vuepages/apis/modules/common.js
  36. +69
    -0
      web_src/vuepages/apis/modules/repos.js
  37. +68
    -15
      web_src/vuepages/langs/config/en-US.js
  38. +71
    -4
      web_src/vuepages/langs/config/zh-CN.js
  39. +123
    -0
      web_src/vuepages/pages/repos/components/ActiveOrgs.vue
  40. +156
    -0
      web_src/vuepages/pages/repos/components/ActiveUsers.vue
  41. +151
    -0
      web_src/vuepages/pages/repos/components/RecommendRepos.vue
  42. +115
    -0
      web_src/vuepages/pages/repos/components/ReposFilters.vue
  43. +316
    -0
      web_src/vuepages/pages/repos/components/ReposItem.vue
  44. +161
    -0
      web_src/vuepages/pages/repos/components/ReposList.vue
  45. +255
    -0
      web_src/vuepages/pages/repos/components/SearchBar.vue
  46. +363
    -0
      web_src/vuepages/pages/repos/components/SquareTop.vue
  47. +123
    -0
      web_src/vuepages/pages/repos/search/index.vue
  48. +17
    -0
      web_src/vuepages/pages/repos/search/vp-repos-search.js
  49. +146
    -0
      web_src/vuepages/pages/repos/square/index.vue
  50. +17
    -0
      web_src/vuepages/pages/repos/square/vp-repos-square.js
  51. +89
    -1
      web_src/vuepages/utils/index.js
  52. +75
    -0
      web_src/vuepages/utils/letteravatar.js
  53. +2
    -2
      webpack.config.js
  54. +2
    -2
      webpack_pro.config.js

+ 21
- 1
models/cloudbrain.go View File

@@ -1862,6 +1862,7 @@ func CreateCloudbrain(cloudbrain *Cloudbrain) (err error) {
session.Commit()

go IncreaseDatasetUseCount(cloudbrain.Uuid)
go OperateRepoAITaskNum(cloudbrain.RepoID, 1)
return nil
}

@@ -2017,9 +2018,29 @@ func DeleteJob(job *Cloudbrain) error {

func deleteJob(e Engine, job *Cloudbrain) error {
_, err := e.ID(job.ID).Delete(job)
if err == nil {
go updateAITaskNumWhenDeleteJob(job)
}
return err
}

func updateAITaskNumWhenDeleteJob(job *Cloudbrain) {
repoId := job.RepoID
if repoId == 0 {
t := &Cloudbrain{}
_, tempErr := x.ID(job.ID).Unscoped().Get(t)
if tempErr != nil {
log.Error("updateAITaskNumWhenDeleteJob error.%v", tempErr)
return
}
repoId = t.RepoID
}

if repoId > 0 {
go OperateRepoAITaskNum(repoId, -1)
}
}

func GetCloudbrainByName(jobName string) (*Cloudbrain, error) {
cb := &Cloudbrain{JobName: jobName}
return getRepoCloudBrain(cb)
@@ -2222,7 +2243,6 @@ func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) {
}

go IncreaseDatasetUseCount(new.Uuid)

return nil
}
func CloudbrainAll(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) {


+ 20
- 0
models/list_options.go View File

@@ -10,6 +10,26 @@ import (
"xorm.io/xorm"
)

type AvailablePageSize int

const (
PageSize15 AvailablePageSize = 15
PageSize30 AvailablePageSize = 30
PageSize50 AvailablePageSize = 50
)

func (s AvailablePageSize) IsLegal() bool {
switch s {
case PageSize30, PageSize50, PageSize15:
return true
}
return false
}

func (s AvailablePageSize) Int() int {
return int(s)
}

// ListOptions options to paginate results
type ListOptions struct {
PageSize int


+ 229
- 4
models/repo.go View File

@@ -231,10 +231,43 @@ type Repository struct {
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

Hot int64 `xorm:"-"`
Active int64 `xorm:"-"`
Alias string `xorm:"INDEX"`
LowerAlias string `xorm:"INDEX"`
Hot int64 `xorm:"-"`
Active int64 `xorm:"-"`
Alias string `xorm:"INDEX"`
LowerAlias string `xorm:"INDEX"`
AiTaskCnt int64 `xorm:"NOT NULL DEFAULT 0"`
ModelCnt int64 `xorm:"NOT NULL DEFAULT 0"`
DatasetCnt int64 `xorm:"NOT NULL DEFAULT 0"`
LastMonthVisits int64 `xorm:"NOT NULL DEFAULT 0"`
LastFourMonthCommits int64 `xorm:"NOT NULL DEFAULT 0"`
}

// Repository4Card format for front display
type Repository4Card struct {
ID int64
OwnerID int64
OwnerName string
LowerName string
Name string
Alias string
NumWatches int
NumStars int
NumForks int
Description string
Topics []string
AiTaskCnt int64
ModelCnt int64
DatasetCnt int64
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
PrimaryLanguage *LanguageStat
RelAvatarLink string
Contributors []*ContributorInfo
IsPrivate bool
IsFork bool
IsMirror bool
IsOwnerPrivate bool
IsArchived bool
}

type RepositoryShow struct {
@@ -243,6 +276,47 @@ type RepositoryShow struct {
Alias string
}

func (repo *Repository) ToCardFormat() *Repository4Card {
link := repo.RelAvatarLink()
var isOwnerPrivate bool
if repo.Owner != nil && repo.Owner.Visibility.IsPrivate() {
isOwnerPrivate = true
}
result := &Repository4Card{
ID: repo.ID,
OwnerID: repo.OwnerID,
OwnerName: repo.OwnerName,
LowerName: repo.LowerName,
Name: repo.Name,
NumWatches: repo.NumWatches,
NumStars: repo.NumStars,
NumForks: repo.NumForks,
Description: repo.Description,
Topics: repo.Topics,
AiTaskCnt: repo.AiTaskCnt,
ModelCnt: repo.ModelCnt,
DatasetCnt: repo.DatasetCnt,
CreatedUnix: repo.CreatedUnix,
UpdatedUnix: repo.UpdatedUnix,
PrimaryLanguage: repo.PrimaryLanguage,
RelAvatarLink: link,
Alias: repo.Alias,
IsPrivate: repo.IsPrivate,
IsFork: repo.IsFork,
IsMirror: repo.IsMirror,
IsOwnerPrivate: isOwnerPrivate,
IsArchived: repo.IsArchived,
}
return result
}

type ContributorInfo struct {
RelAvatarLink string
UserName string
Email string
CommitCnt int
}

// SanitizedOriginalURL returns a sanitized OriginalURL
func (repo *Repository) SanitizedOriginalURL() string {
if repo.OriginalURL == "" {
@@ -2379,6 +2453,75 @@ func CheckRepoStats(ctx context.Context) error {
}
}
// ***** END: Repository.NumForks *****

// ***** START: Repository.DatasetCnt *****
desc = "repository count 'dataset_cnt'"
results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.dataset_cnt!=(select count(1) from attachment inner join dataset on attachment.dataset_id = dataset.id where dataset.repo_id = repository.id)")
if err != nil {
log.Error("Select %s: %v", desc, err)
} else {
for _, result := range results {
id := com.StrTo(result["id"]).MustInt64()
select {
case <-ctx.Done():
log.Warn("CheckRepoStats: Cancelled")
return ErrCancelledf("during %s for repo ID %d", desc, id)
default:
}
log.Trace("Updating %s: %d", desc, id)
err = ResetRepoDatasetNum(id)
if err != nil {
log.Error("Update %s[%d]: %v", desc, id, err)
}
}
}
// ***** END: Repository.DatasetCnt *****

// ***** START: Repository.ModelCnt *****
desc = "repository count 'model_cnt'"
results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.model_cnt!=(select count(1) from ai_model_manage where repository.id = ai_model_manage.repo_id and ai_model_manage.size > 0 )")
if err != nil {
log.Error("Select %s: %v", desc, err)
} else {
for _, result := range results {
id := com.StrTo(result["id"]).MustInt64()
select {
case <-ctx.Done():
log.Warn("CheckRepoStats: Cancelled")
return ErrCancelledf("during %s for repo ID %d", desc, id)
default:
}
log.Trace("Updating %s: %d", desc, id)
err = ResetRepoModelNum(id)
if err != nil {
log.Error("Update %s[%d]: %v", desc, id, err)
}
}
}
// ***** END: Repository.ModelCnt *****

// ***** START: Repository.AiTaskCnt *****
desc = "repository count 'ai_task_cnt'"
results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.ai_task_cnt!=(select count(1) from cloudbrain where repository.id = cloudbrain.repo_id and (cloudbrain.deleted_at is null or cloudbrain.deleted_at = '0001-01-01 00:00:00') )")
if err != nil {
log.Error("Select %s: %v", desc, err)
} else {
for _, result := range results {
id := com.StrTo(result["id"]).MustInt64()
select {
case <-ctx.Done():
log.Warn("CheckRepoStats: Cancelled")
return ErrCancelledf("during %s for repo ID %d", desc, id)
default:
}
log.Trace("Updating %s: %d", desc, id)
err = ResetRepoAITaskNum(id)
if err != nil {
log.Error("Update %s[%d]: %v", desc, id, err)
}
}
}
// ***** END: Repository.AiTaskCnt *****
return nil
}

@@ -2775,3 +2918,85 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi
}
return &RepoFile{CommitId: commitId, Content: d}, nil
}

func ResetRepoAITaskNum(repoId int64) error {
n, err := x.Where("repo_id = ? ", repoId).Count(&Cloudbrain{})
if err != nil {
return err
}
r := Repository{
AiTaskCnt: n,
}
_, err = x.Cols("ai_task_cnt").Where("id = ?", repoId).Update(&r)
return err
}

func ResetRepoDatasetNum(repoId int64) error {
n, err := x.Table("attachment").Join("inner", "dataset", "attachment.dataset_id = dataset.id").Where("dataset.repo_id = ?", repoId).Count()
if err != nil {
return err
}
r := Repository{
DatasetCnt: n,
}
_, err = x.Cols("dataset_cnt").Where("id = ?", repoId).Update(&r)
return err
}

func ResetRepoModelNum(repoId int64) error {
_, err := x.Exec("update repository set model_cnt = (select count(1) from ai_model_manage where ai_model_manage.repo_id = ? and size > 0) where id = ?", repoId, repoId)
return err
}

func operateRepoCol(repoId int64, colName string, amount int64, engines ...*xorm.Engine) error {
var err error

if amount == 0 {
return nil
}
var ee *xorm.Engine
if len(engines) == 0 {
ee = x
} else {
ee = engines[0]
}
if amount > 0 {
_, err = ee.Exec(fmt.Sprintf("update repository set %s = %s + ? where id = ?", colName, colName), amount, repoId)
} else {
_, err = ee.Exec(fmt.Sprintf("update repository set %s = %s - ? where id = ?", colName, colName), -1*amount, repoId)
}

return err
}

func OperateRepoDatasetNum(repoId int64, amount int64, engines ...*xorm.Engine) error {
return operateRepoCol(repoId, "dataset_cnt", amount, engines...)
}

func OperateRepoModelNum(repoId int64, amount int64, engines ...*xorm.Engine) error {
return operateRepoCol(repoId, "model_cnt", amount, engines...)
}

func OperateRepoAITaskNum(repoId int64, amount int64, engines ...*xorm.Engine) error {
return operateRepoCol(repoId, "ai_task_cnt", amount, engines...)
}

func UpdateRepositoryLastFourMonthCommits(repoID int64, amount int64) error {
_, err := x.Exec("update repository set last_four_month_commits = ? where id = ?", amount, repoID)
return err
}
func UpdateRepositoryLastMonthVisits(repoID int64, amount int64) error {
_, err := x.Exec("update repository set last_month_visits = ? where id = ?", amount, repoID)
return err
}

func SyncStatDataToRepo(repo *Repository) {
//Save the visit number of repository in the last month
if lv, err := SumLastMonthNumVisits(repo.ID); err == nil {
UpdateRepositoryLastMonthVisits(repo.ID, lv)
}
//Save the commits number of repository in the last four month
if lc, err := SumLastFourMonthNumCommits(repo.ID); err == nil {
UpdateRepositoryLastFourMonthCommits(repo.ID, lc)
}
}

+ 33
- 21
models/repo_list.go View File

@@ -201,29 +201,41 @@ func (s SearchOrderBy) String() string {
return string(s)
}

type FindReposResponse struct {
Repos []*Repository4Card
Page int
PageSize int
Total int64
}

// Strings for sorting result
const (
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC"
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
SearchOrderBySize SearchOrderBy = "size ASC"
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
SearchOrderByID SearchOrderBy = "id ASC"
SearchOrderByIDReverse SearchOrderBy = "id DESC"
SearchOrderByStars SearchOrderBy = "num_stars ASC"
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC"
SearchOrderByUseCount SearchOrderBy = "use_count ASC"
SearchOrderByUseCountReverse SearchOrderBy = "use_count DESC"
SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC"
SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC"
SearchOrderByWatches SearchOrderBy = "num_watches DESC"
SearchOrderByDefault SearchOrderBy = "recommend desc,num_stars DESC,updated_unix DESC"
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC"
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
SearchOrderBySize SearchOrderBy = "size ASC"
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
SearchOrderByID SearchOrderBy = "id ASC"
SearchOrderByIDReverse SearchOrderBy = "id DESC"
SearchOrderByStars SearchOrderBy = "num_stars ASC"
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
SearchOrderByForks SearchOrderBy = "num_forks ASC"
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC"
SearchOrderByUseCount SearchOrderBy = "use_count ASC"
SearchOrderByUseCountReverse SearchOrderBy = "use_count DESC"
SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC"
SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC"
SearchOrderByWatches SearchOrderBy = "num_watches DESC"
SearchOrderByDefault SearchOrderBy = "recommend desc,num_stars DESC,updated_unix DESC"
SearchOrderByAiTaskCntReverse SearchOrderBy = "ai_task_cnt desc"
SearchOrderByModelCntReverse SearchOrderBy = "model_cnt desc"
SearchOrderByDatasetCntReverse SearchOrderBy = "dataset_cnt desc"
SearchOrderByLastMonthVisitsReverse SearchOrderBy = "last_month_visits desc"
SearchOrderByLastFourMonthCommitsReverse SearchOrderBy = "last_four_month_commits desc"
)

// SearchRepositoryCondition creates a query condition according search repository options


+ 20
- 0
models/repo_statistic.go View File

@@ -200,3 +200,23 @@ func UpdateRepoStatVisits(repoStat *RepoStatistic) error {
_, err := xStatistic.Exec(sql, repoStat.NumVisits, repoStat.RepoID, repoStat.Date)
return err
}

func SumRepoStatColumn(begin, end time.Time, repoId int64, columnName string) (int64, error) {
res, err := xStatistic.Where("created_unix <= ? and created_unix >= ? and repo_id = ? ", end.Unix(), begin.Unix(), repoId).Sum(&RepoStatistic{}, columnName)
if err != nil {
return 0, err
}
return int64(res), nil
}

func SumLastMonthNumVisits(repoId int64) (int64, error) {
end := time.Now()
begin := end.AddDate(0, 0, -30)
return SumRepoStatColumn(begin, end, repoId, "num_visits")
}

func SumLastFourMonthNumCommits(repoId int64) (int64, error) {
end := time.Now()
begin := end.AddDate(0, 0, -120)
return SumRepoStatColumn(begin, end, repoId, "num_commits_added")
}

+ 31
- 0
models/repo_tag.go View File

@@ -4,6 +4,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"fmt"
"xorm.io/builder"
)

type OfficialTag struct {
@@ -166,3 +167,33 @@ func GetAllOfficialTags() ([]OfficialTag, error) {
}
return o, nil
}

type FindSelectedReposOpts struct {
ListOptions
OrgId int64
OnlyPublic bool
}

func GetSelectedRepos(opts FindSelectedReposOpts) ([]*Repository, error) {
if opts.Page < 1 {
opts.Page = 1
}
var cond = builder.NewCond()
cond = cond.And(builder.Eq{"official_tag.code": "selected"})
if opts.OrgId > 0 {
cond = cond.And(builder.Eq{"official_tag_repos.org_id": opts.OrgId})
}
if opts.OnlyPublic {
cond = cond.And(builder.Eq{"repository.is_private": false})
}
t := make([]*Repository, 0)
err := x.Join("inner", "official_tag_repos", "repository.id = official_tag_repos.repo_id").
Join("inner", "official_tag", "official_tag.id = official_tag_repos.tag_id").
Where(cond).OrderBy("repository.updated_unix desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&t)

if err != nil {
return nil, err
}

return t, nil
}

+ 14
- 0
models/topic.go View File

@@ -9,6 +9,7 @@ import (
"regexp"
"strings"
"unicode/utf8"
"xorm.io/xorm"

"code.gitea.io/gitea/modules/timeutil"

@@ -337,3 +338,16 @@ func GetOrgTopics(orgId int64) ([]Topic, error) {

return result, nil
}

func UpdateRepoTopics(repoID int64, topicNames []string, sess ...*xorm.Engine) error {
e := x
if len(sess) > 0 {
e = sess[0]
}
if _, err := e.ID(repoID).Cols("topics").Update(&Repository{
Topics: topicNames,
}); err != nil {
return err
}
return nil
}

+ 34
- 0
models/user.go View File

@@ -198,6 +198,40 @@ type SearchOrganizationsOptions struct {
All bool
}

type User4Front struct {
ID int64
LowerName string `xorm:"UNIQUE NOT NULL"`
Name string `xorm:"UNIQUE NOT NULL"`
FullName string
Email string `xorm:"NOT NULL"`
Language string `xorm:"VARCHAR(5)"`
Description string
RelAvatarLink string
NumMembers int
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}

func (u *User) ToFrontFormat() *User4Front {
uf := &User4Front{
ID: u.ID,
LowerName: u.LowerName,
Name: u.Name,
FullName: u.FullName,
Email: u.Email,
Language: u.Language,
Description: u.Description,
CreatedUnix: u.CreatedUnix,
UpdatedUnix: u.UpdatedUnix,
NumMembers: u.NumMembers,
}
if !u.KeepEmailPrivate {
uf.Email = u.Email
}
uf.RelAvatarLink = u.RelAvatarLink()
return uf
}

// GenerateRandomAvatar generates a random avatar for user.
func (u *User) IsBindWechat() bool {
return u.WechatOpenId != ""


+ 6
- 0
models/user_business_analysis.go View File

@@ -2443,3 +2443,9 @@ func GetContentFromPromote(url string) (string, error) {
allLineStr := string(bytes)
return allLineStr, nil
}

func QueryLast30DaysHighestIndexUsers(size int) ([]int64, error) {
userIds := make([]int64, 0)
err := xStatistic.Table("user_business_analysis_last30_day").Cols("id").OrderBy("user_index desc").Limit(size).Find(&userIds)
return userIds, err
}

+ 9
- 0
modules/redis/redis_key/repo_redis_key.go View File

@@ -0,0 +1,9 @@
package redis_key

import "fmt"

const REPO_PREFIX = "repo"

func RepoTopNContributors(repoId int64, N int) string {
return KeyJoin(REPO_PREFIX, fmt.Sprint(repoId), fmt.Sprint(N), "top_n_contributor")
}

+ 8
- 0
modules/setting/setting.go View File

@@ -677,6 +677,10 @@ var (
CloudbrainStoppedTitle string
CloudbrainStoppedRemark string

//repo square config
IncubationSourceOrgName string
PaperRepoTopicName string

//nginx proxy
PROXYURL string
RadarMap = struct {
@@ -1585,6 +1589,10 @@ func NewContext() {
CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果")
CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。")

sec = Cfg.Section("repo-square")
IncubationSourceOrgName = sec.Key("INCUBATION_ORG_NAME").MustString("OpenI")
PaperRepoTopicName = sec.Key("PAPER_REPO_TOPIC_NAME").MustString("openi-paper")

sec = Cfg.Section("point")
CloudBrainPaySwitch = sec.Key("CLOUDBRAIN_PAY_SWITCH").MustBool(false)
CloudBrainPayDelay = sec.Key("CLOUDBRAIN_PAY_DELAY").MustDuration(30 * time.Minute)


+ 2
- 1
package.json View File

@@ -17,11 +17,12 @@
"core-js": "3.6.5",
"css-loader": "3.5.3",
"cssnano": "4.1.10",
"dayjs": "1.10.7",
"domino": "2.1.5",
"dropzone": "5.7.2",
"echarts": "3.8.5",
"element-ui": "2.15.5",
"esdk-obs-browserjs": "3.20.7",
"esdk-obs-browserjs": "3.22.3",
"esdk-obs-nodejs": "3.20.11",
"fast-glob": "3.2.2",
"file-loader": "6.0.0",


+ 3
- 11
public/home/home.js View File

@@ -622,20 +622,12 @@ function displayRepo(json){
for (var i = 0, iLen = repos.length; i < iLen; i++) {
if (i >= 4) break;
var repo = repos[i];
// <i class="ri-star-line"></i>${repo["NumStars"]}<i class="ri-git-branch-line am-ml-10"></i>${repo["NumForks"]}</span> <div class="ui tags nowrap am-mt-10"></div>
html += `<div class="ui fluid card" style="border-radius:6px;">
<div class="content">
${repo["Avatar"] ? `<img class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img class="left floated mini ui image" avatar="${repo["OwnerName"]}">`}
<div class="content" style="position:relative;">
${repo["Avatar"] ? `<img style="border-radius:100%;" class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img style="border-radius:100%;" class="left floated mini ui image" avatar="${repo["OwnerName"]}">`}
<a class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;" href="/${repo["OwnerName"]}/${repo["Name"]}" title="${repo["Alias"]}">${repo["Alias"]}</a>
<div class="description nowrap-2" style="rgba(136,136,136,1);;font-size:12px;" title="${repo["Description"]}">${repo["Description"]}</div>
`;
// if (repo["Topics"] != null) {
// for(var j = 0; j < repo["Topics"].length; j++){
// var topic = repo["Topics"][j];
// var url = "/explore/repos?q=" + (topic) + "&amp;topic="
// html += `<a class="ui small label topic" href=" ${url}">${topic}</a>`;
// }
// }
<a href="/${repo["OwnerName"]}/${repo["Name"]}" style="height:100%;width:100%;position:absolute;left:0;top:0"></a>`;
html += `
</div>
</div>`;


+ 34
- 0
routers/admin/resources.go View File

@@ -307,3 +307,37 @@ func RefreshHistorySpec(ctx *context.Context) {
r["total"] = total
ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

func RefreshReposHistoryCnt(ctx *context.Context) {
scope := ctx.Query("scope")
list := ctx.Query("list")

var scopeAll = false
if scope == "all" {
scopeAll = true
}
var ids = make([]int64, 0)
if list != "" {
strs := strings.Split(list, "|")
for _, s := range strs {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ids = append(ids, i)
}

}

total, success, err := resource.RefreshHistorySpec(scopeAll, ids)
if err != nil {
log.Error("RefreshHistorySpec error. %v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
r := make(map[string]interface{}, 0)
r["success"] = success
r["total"] = total
ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

+ 14
- 2
routers/api/v1/repo/topic.go View File

@@ -177,13 +177,25 @@ func AddTopic(ctx *context.APIContext) {
return
}

_, err = models.AddTopic(ctx.Repo.Repository.ID, topicName)
topic, err := models.AddTopic(ctx.Repo.Repository.ID, topicName)
if err != nil {
log.Error("AddTopic failed: %v", err)
ctx.InternalServerError(err)
return
}

found := false
topicNames := make([]string, len(topics))
for i, t := range topics {
topicNames[i] = t.Name
if strings.EqualFold(topic.Name, t.Name) {
found = true
break
}
}
if !found && topic.Name != "" {
topicNames = append(topicNames, topic.Name)
}
models.UpdateRepoTopics(ctx.Repo.Repository.ID, topicNames)
ctx.Status(http.StatusNoContent)
}



+ 106
- 0
routers/home.go View File

@@ -7,6 +7,7 @@ package routers

import (
"bytes"
"code.gitea.io/gitea/routers/response"
"encoding/json"
"net/http"
"strconv"
@@ -43,6 +44,8 @@ const (
tplHomeTerm base.TplName = "terms"
tplHomePrivacy base.TplName = "privacy"
tplResoruceDesc base.TplName = "resource_desc"
tplRepoSquare base.TplName = "explore/repos/square"
tplRepoSearch base.TplName = "explore/repos/search"
)

// Home render home page
@@ -296,6 +299,109 @@ func ExploreRepos(ctx *context.Context) {
})
}

func GetRepoSquarePage(ctx *context.Context) {
ctx.Data["SquareBanners"] = repository.GetBanners()
ctx.Data["SquareTopics"] = repository.GetTopics()
ctx.Data["SquareRecommendRepos"] = repository.GetRecommendRepos()

repos, _ := repository.GetPreferredRepos()
ctx.Data["SquarePreferredRepos"] = repos
ctx.HTML(200, tplRepoSquare)
}
func GetRepoSearchPage(ctx *context.Context) {
ctx.Data["SquareTopics"] = repository.GetTopics()
ctx.HTML(200, tplRepoSearch)
}

func RepoSquare(ctx *context.Context) {
var result []*models.Repository4Card
var err error
switch ctx.Query("type") {
case "preferred":
result, err = repository.GetPreferredRepos()
case "incubation":
result, err = repository.GetIncubationRepos()
case "hot-paper":
result, err = repository.GetHotPaperRepos()
default:
result, err = repository.GetPreferredRepos()
}
if err != nil {
ctx.JSON(http.StatusOK, response.ResponseError(err))
return
}
resultMap := make(map[string]interface{}, 0)
resultMap["Repos"] = result
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
}

func ActiveUser(ctx *context.Context) {
var err error
var currentUserId int64
if ctx.User != nil {
currentUserId = ctx.User.ID
}
result, err := repository.GetActiveUser4Square(currentUserId)
if err != nil {
log.Error("ActiveUser err. %v", err)
ctx.JSON(http.StatusOK, response.Success())
return
}
resultMap := make(map[string]interface{}, 0)
resultMap["Users"] = result
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
}
func ActiveOrg(ctx *context.Context) {
result, err := repository.GetActiveOrgs()
if err != nil {
log.Error("ActiveOrg err. %v", err)
ctx.JSON(http.StatusOK, response.Success())
return
}
resultMap := make(map[string]interface{}, 0)
resultMap["Orgs"] = result
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
}

func RepoFind(ctx *context.Context) {
keyword := strings.Trim(ctx.Query("q"), " ")
topic := strings.Trim(ctx.Query("topic"), " ")
sort := strings.Trim(ctx.Query("sort"), " ")
page := ctx.QueryInt("page")
pageSize := ctx.QueryInt("pageSize")
if pageSize == 0 {
pageSize = 15
}
if pageSize > 100 {
ctx.JSON(http.StatusOK, response.ServerError("pageSize illegal"))
return
}
if page <= 0 {
page = 1
}

var ownerID int64
if ctx.User != nil && !ctx.User.IsAdmin {
ownerID = ctx.User.ID
}

result, err := repository.FindRepos(repository.FindReposOptions{
ListOptions: models.ListOptions{Page: page, PageSize: pageSize},
Actor: ctx.User,
Sort: sort,
Keyword: keyword,
Topic: topic,
Private: ctx.User != nil,
OwnerID: ownerID,
})
if err != nil {
log.Error("RepoFind error. %v", err)
ctx.JSON(http.StatusOK, response.ResponseError(err))
return
}
ctx.JSON(http.StatusOK, response.SuccessWithData(result))
}

func ExploreDatasets(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("explore")
ctx.Data["PageIsExplore"] = true


+ 3
- 0
routers/private/internal.go View File

@@ -6,6 +6,7 @@
package private

import (
"code.gitea.io/gitea/services/repository"
"strings"

"code.gitea.io/gitea/routers/admin"
@@ -55,7 +56,9 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/task/history_handle/duration", repo.HandleTaskWithNoDuration)
m.Post("/task/history_handle/aicenter", repo.HandleTaskWithAiCenter)
m.Post("/resources/specification/handle_historical_task", admin.RefreshHistorySpec)
m.Post("/repos/cnt_stat/handle_historical_task", admin.RefreshHistorySpec)
m.Post("/duration_statisctic/history_handle", repo.CloudbrainUpdateHistoryData)
m.Post("/square/repo/stat/refresh", repository.RefreshRepoStatData)

}, CheckInternalToken)
}

+ 23
- 4
routers/repo/ai_model_manage.go View File

@@ -2,6 +2,7 @@ package repo

import (
"archive/zip"
"code.gitea.io/gitea/services/repository"
"encoding/json"
"errors"
"fmt"
@@ -170,10 +171,17 @@ func updateStatus(id string, modelSize int64, status int, modelPath string, stat
if len(statusDesc) > 400 {
statusDesc = statusDesc[0:400]
}
m, _ := models.QueryModelById(id)
err := models.ModifyModelStatus(id, modelSize, status, modelPath, statusDesc)
if err != nil {
log.Info("update status error." + err.Error())
}
if m != nil {
if modelSize > 0 && m.Size == 0 {
go repository.ResetRepoModelNum(m.RepoId)
}
}

}

func SaveNewNameModel(ctx *context.Context) {
@@ -308,13 +316,14 @@ func getSize(files []storage.FileInfo) int64 {
func UpdateModelSize(modeluuid string) {
model, err := models.QueryModelById(modeluuid)
if err == nil {
var size int64
if model.Type == models.TypeCloudBrainOne {
if strings.HasPrefix(model.Path, setting.Attachment.Minio.Bucket+"/"+Model_prefix) {
files, err := storage.GetAllObjectByBucketAndPrefixMinio(setting.Attachment.Minio.Bucket, model.Path[len(setting.Attachment.Minio.Bucket)+1:])
if err != nil {
log.Info("Failed to query model size from minio. id=" + modeluuid)
}
size := getSize(files)
size = getSize(files)
models.ModifyModelSize(modeluuid, size)
}
} else if model.Type == models.TypeCloudBrainTwo {
@@ -323,10 +332,13 @@ func UpdateModelSize(modeluuid string) {
if err != nil {
log.Info("Failed to query model size from obs. id=" + modeluuid)
}
size := getSize(files)
size = getSize(files)
models.ModifyModelSize(modeluuid, size)
}
}
if model.Size == 0 && size > 0 {
go repository.ResetRepoModelNum(model.RepoId)
}
} else {
log.Info("not found model,uuid=" + modeluuid)
}
@@ -441,13 +453,14 @@ func DeleteModelFile(ctx *context.Context) {
fileName := ctx.Query("fileName")
model, err := models.QueryModelById(id)
if err == nil {
var totalSize int64
if model.ModelType == MODEL_LOCAL_TYPE {
if model.Type == models.TypeCloudBrainOne {
bucketName := setting.Attachment.Minio.Bucket
objectName := model.Path[len(bucketName)+1:] + fileName
log.Info("delete bucket=" + bucketName + " path=" + objectName)
if strings.HasPrefix(model.Path, bucketName+"/"+Model_prefix) {
totalSize := storage.MinioGetFilesSize(bucketName, []string{objectName})
totalSize = storage.MinioGetFilesSize(bucketName, []string{objectName})
err := storage.Attachments.DeleteDir(objectName)
if err != nil {
log.Info("Failed to delete model. id=" + id)
@@ -467,7 +480,7 @@ func DeleteModelFile(ctx *context.Context) {
objectName := model.Path[len(setting.Bucket)+1:] + fileName
log.Info("delete bucket=" + setting.Bucket + " path=" + objectName)
if strings.HasPrefix(model.Path, bucketName+"/"+Model_prefix) {
totalSize := storage.ObsGetFilesSize(bucketName, []string{objectName})
totalSize = storage.ObsGetFilesSize(bucketName, []string{objectName})
err := storage.ObsRemoveObject(bucketName, objectName)
if err != nil {
log.Info("Failed to delete model. id=" + id)
@@ -484,6 +497,9 @@ func DeleteModelFile(ctx *context.Context) {
}
}
}
if (model.Size - totalSize) <= 0 {
go repository.ResetRepoModelNum(model.RepoId)
}
}
ctx.JSON(200, map[string]string{
"code": "0",
@@ -552,6 +568,9 @@ func deleteModelByID(ctx *context.Context, id string) error {
}
}
}
if model.Size > 0 {
go repository.ResetRepoModelNum(model.RepoId)
}
}
}
return err


+ 3
- 0
routers/repo/attachment.go View File

@@ -29,6 +29,7 @@ import (
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/worker"
repo_service "code.gitea.io/gitea/services/repository"
gouuid "github.com/satori/go.uuid"
)

@@ -180,6 +181,7 @@ func DeleteAttachment(ctx *context.Context) {
ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err))
return
}
go repo_service.DecreaseRepoDatasetNum(attach.DatasetID)

attachjson, _ := json.Marshal(attach)
labelmsg.SendDeleteAttachToLabelSys(string(attachjson))
@@ -894,6 +896,7 @@ func CompleteMultipart(ctx *context.Context) {
return
}
attachment.UpdateDatasetUpdateUnix()
go repo_service.IncreaseRepoDatasetNum(dataset.ID)
repository, _ := models.GetRepositoryByID(dataset.RepoID)
notification.NotifyOtherTask(ctx.User, repository, fmt.Sprint(repository.IsPrivate, attachment.IsPrivate), attachment.Name, models.ActionUploadAttachment)
if attachment.DatasetID != 0 {


+ 7
- 1
routers/repo/cloudbrain_statistic.go View File

@@ -14,7 +14,13 @@ import (
)

func CloudbrainDurationStatisticHour() {
if setting.IsCloudbrainTimingEnabled {
defer func() {
err := recover()
if err == nil {
return
}
}()
if setting.IsCloudbrainTimingEnabled {
var statisticTime time.Time
var count int64
recordDurationUpdateTime, err := models.GetDurationRecordUpdateTime()


+ 2
- 0
routers/repo/repo_statistic.go View File

@@ -166,6 +166,8 @@ func RepoStatisticDaily(date string) {
repoStat.NumIssuesGrowth = repoStat.NumIssues - repoStatisticFourMonthsAgo.NumIssues
}

models.SyncStatDataToRepo(repo)

if _, err = models.InsertRepoStat(&repoStat); err != nil {
log.Error("InsertRepoStat failed(%s): %v", projectName, err)
log.Error("failed statistic: %s", projectName)


+ 12
- 1
routers/routes/routes.go View File

@@ -371,7 +371,18 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/images/custom", repo.GetCustomImages)
m.Get("/images/star", repo.GetStarImages)

m.Get("/repos", routers.ExploreRepos)
m.Group("/repos", func() {
//m.Get("", routers.ExploreRepos)
m.Get("", routers.GetRepoSearchPage)
m.Group("/square", func() {
m.Get("", routers.GetRepoSquarePage)
m.Get("/tab", routers.RepoSquare)
m.Get("/active-user", routers.ActiveUser)
m.Get("/active-org", routers.ActiveOrg)
})

m.Get("/search", routers.RepoFind)
})
m.Get("/datasets", routers.ExploreDatasets)
m.Get("/users", routers.ExploreUsers)
m.Get("/organizations", routers.ExploreOrganizations)


+ 88
- 0
services/repository/contributor.go View File

@@ -0,0 +1,88 @@
package repository

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"encoding/json"
"github.com/patrickmn/go-cache"
"time"
)

var repoContributorCache = cache.New(5*time.Minute, 1*time.Minute)

type ContributorCacheVal struct {
Contributors []*models.ContributorInfo
Total int
}

func GetRepoTopNContributors(repo *models.Repository, N int) ([]*models.ContributorInfo, int) {
val, _ := redis_client.Get(redis_key.RepoTopNContributors(repo.ID, N))
if val != "" {
log.Debug("Get RepoTopNContributors from redis,repo.ID = %d value = %v", repo.ID, val)
temp := &ContributorCacheVal{}
json.Unmarshal([]byte(val), temp)
return temp.Contributors, temp.Total
}

contributorInfos, total := getRepoTopNContributorsFromDisk(repo, N)
log.Debug("Get RepoTopNContributors from disk,repo.ID = %d ", repo.ID)
jsonVal, err := json.Marshal(&ContributorCacheVal{Contributors: contributorInfos, Total: total})
if err == nil {
redis_client.Setex(redis_key.RepoTopNContributors(repo.ID, N), string(jsonVal), 2*time.Minute)
}
return contributorInfos, total
}

func getRepoTopNContributorsFromDisk(repo *models.Repository, N int) ([]*models.ContributorInfo, int) {
contributorInfos := make([]*models.ContributorInfo, 0)

branchName := GetDefaultBranchName(repo)
if branchName == "" {
return contributorInfos, 0
}

contributors, err := git.GetContributors(repo.RepoPath(), branchName)
if err == nil && contributors != nil {
contributorInfoHash := make(map[string]*models.ContributorInfo)
for _, c := range contributors {
if len(contributorInfos) >= N {
break
}
if c.Email == "" {
continue
}
// get user info from committer email
user, err := models.GetUserByActivateEmail(c.Email)
if err == nil {
// committer is system user, get info through user's primary email
if existedContributorInfo, ok := contributorInfoHash[user.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
} else {
// new committer info
var newContributor = &models.ContributorInfo{
user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt,
}
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[user.Email] = newContributor
}
} else {
// committer is not system user
if existedContributorInfo, ok := contributorInfoHash[c.Email]; ok {
// existed: same primary email, different committer name
existedContributorInfo.CommitCnt += c.CommitCnt
} else {
var newContributor = &models.ContributorInfo{
"", "", c.Email, c.CommitCnt,
}
contributorInfos = append(contributorInfos, newContributor)
contributorInfoHash[c.Email] = newContributor
}
}
}
}
return contributorInfos, len(contributors)
}

+ 51
- 6
services/repository/repository.go View File

@@ -5,18 +5,19 @@
package repository

import (
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
pull_service "code.gitea.io/gitea/services/pull"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
"xorm.io/xorm"
)

const SHELL_FLAG_ON = 1
@@ -328,3 +329,47 @@ func IsUploadFileInvalidErr(err error) bool {
_, ok := err.(UploadFileInvalidErr)
return ok
}

func IncreaseRepoDatasetNum(datasetID int64, engines ...*xorm.Engine) error {
dataset, err := models.GetDatasetByID(datasetID)
if err != nil {
return err
}
return models.OperateRepoDatasetNum(dataset.RepoID, 1, engines...)
}

func IncreaseRepoModelNum(repoId int64, engines ...*xorm.Engine) error {
return models.OperateRepoModelNum(repoId, 1, engines...)
}

func ResetRepoModelNum(repoId int64) error {
return models.ResetRepoModelNum(repoId)
}

func DecreaseRepoDatasetNum(datasetID int64, engines ...*xorm.Engine) error {
dataset, err := models.GetDatasetByID(datasetID)
if err != nil {
return err
}
return models.OperateRepoDatasetNum(dataset.RepoID, -1, engines...)
}

func DecreaseRepoModelNum(repoId int64, engines ...*xorm.Engine) error {
return models.OperateRepoModelNum(repoId, -1, engines...)
}

func GetDefaultBranchName(repo *models.Repository) string {
gitRepo, err := git.OpenRepository(repo.RepoPath())
if err != nil {
return ""
}
defer gitRepo.Close()
if len(repo.DefaultBranch) > 0 && gitRepo.IsBranchExist(repo.DefaultBranch) {
return repo.DefaultBranch
}
brs, _, err := gitRepo.GetBranches(0, 0)
if len(brs) > 0 {
return brs[0]
}
return ""
}

+ 315
- 0
services/repository/square.go View File

@@ -0,0 +1,315 @@
package repository

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"encoding/json"
"github.com/patrickmn/go-cache"
"time"
)

var repoSquareCache = cache.New(2*time.Minute, 1*time.Minute)

const (
RREFERED_CACHE = "PreferredRepos"
REPO_BANNER_CACHE = "RepoBanner"
TOPICS_CACHE = "RepoTopics"
RECOMMEND_CACHE = "RecommendRepos"
)

func GetBanners() []map[string]string {
v, success := repoSquareCache.Get(REPO_BANNER_CACHE)
if success {
log.Debug("GetBanners from cache,value = %v", v)
if v == nil {
return nil
}
r := v.([]map[string]string)
return r
}
repoMap := getMapContent("repos/square_banner")
repoSquareCache.Set(REPO_BANNER_CACHE, repoMap, 1*time.Minute)
return repoMap
}

func GetTopics() []string {
v, success := repoSquareCache.Get(TOPICS_CACHE)
if success {
log.Debug("GetTopics from cache,value = %v", v)
if v == nil {
return nil
}
r := v.([]string)
return r
}
topics := getArrayContent("repos/recommend_topics")
repoSquareCache.Set(TOPICS_CACHE, topics, 1*time.Minute)
return topics
}

func getMapContent(fileName string) []map[string]string {
url := setting.RecommentRepoAddr + fileName
result, err := RecommendContentFromPromote(url)
remap := make([]map[string]string, 0)
if err == nil {
json.Unmarshal([]byte(result), &remap)
}
return remap
}

func getArrayContent(fileName string) []string {
url := setting.RecommentRepoAddr + fileName
result, err := RecommendContentFromPromote(url)
r := make([]string, 0)
if err == nil {
json.Unmarshal([]byte(result), &r)
}
return r
}

func GetRecommendRepos() []map[string]interface{} {
v, success := repoSquareCache.Get(RECOMMEND_CACHE)
if success {
log.Debug("GetRecommendRepos from cache,value = %v", v)
if v == nil {
return nil
}
r := v.([]map[string]interface{})
return r
}
repoMap := getMapContent("home/projects")
r, _ := GetRecommendRepoFromPromote(repoMap)
repoSquareCache.Set(RECOMMEND_CACHE, r, 1*time.Minute)
return r
}

func GetPreferredRepos() ([]*models.Repository4Card, error) {
v, success := repoSquareCache.Get(RREFERED_CACHE)
if success {
log.Debug("GetPreferredRepos from cache,value = %v", v)
if v == nil {
return nil, nil
}
r := v.([]*models.Repository4Card)
return r, nil
}

repos, err := models.GetSelectedRepos(models.FindSelectedReposOpts{
ListOptions: models.ListOptions{
PageSize: 10,
Page: 1,
},
OnlyPublic: true,
})
if err != nil {
return nil, err
}
result := make([]*models.Repository4Card, len(repos))
for i, r := range repos {
result[i] = r.ToCardFormat()
}

repoSquareCache.Set(RREFERED_CACHE, result, 1*time.Minute)
return result, nil
}

func GetIncubationRepos() ([]*models.Repository4Card, error) {
org, err := models.GetOrgByName(setting.IncubationSourceOrgName)
if models.IsErrOrgNotExist(err) {
return make([]*models.Repository4Card, 0), nil
}
if err != nil {
return nil, err
}
repos, err := models.GetSelectedRepos(models.FindSelectedReposOpts{
ListOptions: models.ListOptions{
PageSize: 10,
Page: 1,
},
OrgId: org.ID,
OnlyPublic: true,
})
if err != nil {
return nil, err
}
result := make([]*models.Repository4Card, len(repos))
for i, r := range repos {
result[i] = r.ToCardFormat()
}
return result, nil
}

func GetHotPaperRepos() ([]*models.Repository4Card, error) {
rlist, _, err := models.SearchRepository(&models.SearchRepoOptions{
ListOptions: models.ListOptions{
Page: 1,
PageSize: 10,
},
OrderBy: models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated,
TopicOnly: true,
TopicName: setting.PaperRepoTopicName,
AllPublic: true,
})
if err != nil {
return nil, err
}
result := make([]*models.Repository4Card, len(rlist))
for i, r := range rlist {
result[i] = r.ToCardFormat()
}
return result, nil
}

type FindReposOptions struct {
models.ListOptions
Actor *models.User
Sort string
Keyword string
Topic string
Private bool
OwnerID int64
}

func FindRepos(opts FindReposOptions) (*models.FindReposResponse, error) {

var (
repos []*models.Repository
count int64
err error
orderBy models.SearchOrderBy
)

switch opts.Sort {
//1.近期热门:按最近1个月浏览量倒序排序,最近1个月浏览量>最近更新>项目名称升序
case "mostpopular":
orderBy = models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//2.近期活跃:按提交增长量(最近4个月commit数)倒序排序,提交增长量>最近更新>项目名称升序。
case "mostactive":
orderBy = models.SearchOrderByLastFourMonthCommitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//3.最近更新:按最近更新>项目名称升序排序。
case "recentupdate":
orderBy = models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//4.最近创建:按项目创建时间排序,最近的排前面。最近创建>项目名称升序。
case "newest":
orderBy = models.SearchOrderByNewest + "," + models.SearchOrderByAlphabetically
//5.点赞最多:按点赞数倒序排序。点赞数>最近更新>项目名称升序。
case "moststars":
orderBy = models.SearchOrderByStarsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//6.派生最多:按派生数倒序排序。派生数>最近更新>项目名称升序。
case "mostforks":
orderBy = models.SearchOrderByForksReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//7.数据集最多:按项目包含的数据集文件数量倒序排序,数据集文件数>最近更新>项目名称升序。
case "mostdatasets":
orderBy = models.SearchOrderByDatasetCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//8.AI任务最多:按项目包含的AI任务数量倒序排序,AI任务数>最近更新>项目名称升序。
case "mostaitasks":
orderBy = models.SearchOrderByAiTaskCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
//9.模型最多:按项目包含的模型数量倒序排序,模型大小为0则不统计。模型数>最近更新>项目名称升序。
case "mostmodels":
orderBy = models.SearchOrderByModelCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically

default:
orderBy = models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically
}

repos, count, err = models.SearchRepository(&models.SearchRepoOptions{
ListOptions: opts.ListOptions,
Actor: opts.Actor,
OrderBy: orderBy,
Private: opts.Private,
Keyword: opts.Keyword,
OwnerID: opts.OwnerID,
AllPublic: true,
AllLimited: true,
TopicName: opts.Topic,
IncludeDescription: setting.UI.SearchRepoDescription,
})
if err != nil {
log.Error("FindRepos error when SearchRepository.%v", err)
return nil, err
}
result := make([]*models.Repository4Card, len(repos))
for i, r := range repos {
t := r.ToCardFormat()
contributors, _ := GetRepoTopNContributors(r, 6)
t.Contributors = contributors
result[i] = t
}

return &models.FindReposResponse{
Repos: result,
Total: count,
Page: opts.Page,
PageSize: opts.PageSize,
}, nil
}

type ActiveUser struct {
User *models.User4Front
Followed bool
ShowButton bool
}

func GetActiveUser4Square(currentUserId int64) ([]*ActiveUser, error) {
result := make([]*ActiveUser, 0)
userIds, err := models.QueryLast30DaysHighestIndexUsers(5)
if err != nil {
log.Error("ActiveUser err. %v", err)
return result, err
}
if len(userIds) == 0 {
return result, nil
}

users, err := models.GetUsersByIDs(userIds)
if err != nil {
return result, nil
}
usersMap := make(map[int64]*models.User)
for _, v := range users {
usersMap[v.ID] = v
}

for i := 0; i < len(userIds); i++ {
userId := userIds[i]
user := usersMap[userId]
if user == nil {
continue
}
isFollowed := false
if currentUserId != 0 {
isFollowed = models.IsFollowing(currentUserId, userId)
}
a := &ActiveUser{
Followed: isFollowed,
User: user.ToFrontFormat(),
ShowButton: currentUserId != userId,
}
result = append(result, a)
}
return result, nil
}

func GetActiveOrgs() ([]*models.User4Front, error) {
orgScores, err := models.FindTopNOpenIOrgs(5)
if err != nil {
return nil, err
}
orgs := make([]*models.User4Front, len(orgScores))
for i, v := range orgScores {
orgs[i] = v.ToFrontFormat()
}
return orgs, nil
}

func RefreshRepoStatData() {
repos, err := models.GetAllRepositories()
if err != nil {
log.Error("RefreshRepoStatData GetAllRepositories failed: %v", err.Error())
return
}
for _, repo := range repos {
models.SyncStatDataToRepo(repo)
}
}

+ 5
- 5
templates/base/head_navbar.tmpl View File

@@ -35,7 +35,7 @@
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -48,7 +48,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -75,7 +75,7 @@
</div>
</div>

<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -89,7 +89,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu" >
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -100,7 +100,7 @@
</div>
</div>
{{else if .IsLandingPageExplore}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a>
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a>
{{end}}


+ 5
- 5
templates/base/head_navbar_fluid.tmpl View File

@@ -32,7 +32,7 @@
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -45,7 +45,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -71,7 +71,7 @@
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -84,7 +84,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -95,7 +95,7 @@
</div>
</div>
{{else if .IsLandingPageExplore}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a>
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a>
{{end}}


+ 5
- 5
templates/base/head_navbar_home.tmpl View File

@@ -24,7 +24,7 @@
</div>
</div>
</div>
<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -37,7 +37,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -64,7 +64,7 @@
</div>
</div>

<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -77,7 +77,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -88,7 +88,7 @@
</div>
</div>
{{else if .IsLandingPageExplore}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a>
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a>
{{end}}


+ 4
- 4
templates/base/head_navbar_pro.tmpl View File

@@ -34,7 +34,7 @@
</div>
</div>

<a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<div class="ui simple dropdown item" >
{{.i18n.Tr "repo.model_manager"}}
@@ -47,7 +47,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -87,7 +87,7 @@
{{.i18n.Tr "explore"}}
<i class="dropdown icon"></i>
<div class="menu" >
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>-->
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
{{if .IsOperator}}
@@ -98,7 +98,7 @@
</div>
</div>
{{else if .IsLandingPageExplore}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a>
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a>
{{end}}


+ 2
- 2
templates/explore/navbar.tmpl View File

@@ -1,6 +1,6 @@
<div class="tablet only mobile only sixteen wide mobile sixteen wide tablet column row">
<div class="ui secondary pointing tabular top attached borderless menu navbar">
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos">
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos/square">
{{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}}
</a>
<a class="{{if .PageIsDatasets}}active{{end}} item" href="{{AppSubUrl}}/explore/datasets">
@@ -24,7 +24,7 @@
<div class="computer only three wide computer column">
<div class="ui grid">
<div class="sixteen wide column ui secondary sticky pointing tabular vertical menu">
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos">
<a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos/square">
{{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}}
</a>
<a class="{{if .PageIsDatasets}}active{{end}} item" href="{{AppSubUrl}}/explore/datasets">


+ 8
- 0
templates/explore/repos/search.tmpl View File

@@ -0,0 +1,8 @@
{{template "base/head_home" .}}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-repos-search.css?v={{MD5 AppVer}}" />
<script>
var staticSquareTopics = {{ .SquareTopics }};
</script>
<div id="__vue-root"></div>
<script src="{{StaticUrlPrefix}}/js/vp-repos-search.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 16
- 0
templates/explore/repos/square.tmpl View File

@@ -0,0 +1,16 @@
{{template "base/head_home" .}}
{{ if .SquareBanners }}
{{ range .SquareBanners }}
<img preload style="height:0;width:0;position:absolute;left:-2000px;" src="{{.src}}" />
{{ end }}
{{ end }}
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-repos-square.css?v={{MD5 AppVer}}" />
<script>
var staticSquareBanners = {{ .SquareBanners }};
var staticSquarePreferredRepos = {{ .SquarePreferredRepos }};
var staticSquareTopics = {{ .SquareTopics }};
var staticSquareRecommendRepos = {{ .SquareRecommendRepos }};
</script>
<div id="__vue-root"></div>
<script src="{{StaticUrlPrefix}}/js/vp-repos-square.js?v={{MD5 AppVer}}"></script>
{{template "base/footer" .}}

+ 1
- 1
templates/user/dashboard/dashboard.tmpl View File

@@ -10,7 +10,7 @@
{{.i18n.Tr "home.wecome_AI_plt"}}
</div>
<div class="content">
<p class="ui text grey">{{.i18n.Tr "home.explore_AI"}} <a href="{{AppSubUrl}}/explore/repos"> {{.i18n.Tr "home.repositories"}}</a> {{.i18n.Tr "home.or_t"}} <a href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "home.datasets"}}</a></p>
<p class="ui text grey">{{.i18n.Tr "home.explore_AI"}} <a href="{{AppSubUrl}}/explore/repos/square"> {{.i18n.Tr "home.repositories"}}</a> {{.i18n.Tr "home.or_t"}} <a href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "home.datasets"}}</a></p>
<p><span class="ui text grey">{{.i18n.Tr "home.use_plt__fuction"}}</span>&nbsp;<a class="mini ui blue button" href="{{AppSubUrl}}/repo/create{{if .ContextUser.IsOrganization}}?org={{.ContextUser.ID}}{{end}}" >{{.i18n.Tr "repo.create_repo"}}</a></p>
<p class="ui text grey">{{.i18n.Tr "home.provide_resoure"}}</p>
</div>


+ 2
- 2
web_src/js/index.js View File

@@ -50,7 +50,7 @@ import initImage from "./features/images.js";
import selectDataset from "./components/dataset/selectDataset.vue";
import referenceDataset from "./components/dataset/referenceDataset.vue";
// import $ from 'jquery.js'
import router from "./router/index.js";
// import router from "./router/index.js";
import { Message } from "element-ui";

import { i18nVue } from "./features/i18nVue.js";
@@ -5214,7 +5214,7 @@ function initTopToHome() {
$(window).scroll(function (e) {
const scrollTop = $(document).scrollTop();
const winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
if (scrollTop > winHeight * 1.2) {
if (scrollTop > winHeight * 0.5) {
topToHomeEl.fadeIn();
} else {
topToHomeEl.fadeOut();


+ 12
- 0
web_src/vuepages/apis/modules/common.js View File

@@ -0,0 +1,12 @@
import service from '../service';

// 获取promote配置数据
export const getPromoteData = (filePathName) => {
return service({
url: '/dashboard/invitation',
method: 'get',
params: {
filename: filePathName
},
});
}

+ 69
- 0
web_src/vuepages/apis/modules/repos.js View File

@@ -0,0 +1,69 @@
import service from '../service';

// 获取首页数据
export const getHomePageData = () => {
return service({
url: '/recommend/home',
method: 'get',
params: {},
});
}

// 获取项目广场上方tab数据 tab=preferred 项目优选|incubation 启智孵化管道|hot-paper 热门论文项目
export const getReposSquareTabData = (tab) => {
return service({
url: '/explore/repos/square/tab',
method: 'get',
params: {
type: tab
},
});
}

// 搜索项目
// q string 否 关键词
// topic string 否 标签名
// sort string 是 mostpopular 近期热门 | mostactive 近期活跃 | recentupdate 最近更新 | newest 最近创建
// moststars 点赞最多 | mostforks 派生最多 | mostdatasets 数据集最多 | mostaitasks AI任务最多 | mostmodels 模型最多
// pageSize int 是 每页大小,可选值为15 | 30 | 50
// page int 是 页码
export const getReposListData = (params) => {
return service({
url: '/explore/repos/search',
method: 'get',
params: {
q: params.q || '',
topic: params.topic || '',
sort: params.sort || 'mostpopular',
pageSize: params.pageSize || 15,
page: params.page || 1,
},
});
}

// 获取活跃用户列表
export const getActiveUsers = () => {
return service({
url: '/explore/repos/square/active-user',
method: 'get',
params: {},
});
}

// 关注用户
export const followingUsers = (userName, isFollowing) => {
return service({
url: `/api/v1/user/following/${userName}`,
method: isFollowing ? 'put' : 'delete',
params: {},
});
}

// 获取活跃组织列表
export const getActiveOrgs = () => {
return service({
url: '/explore/repos/square/active-org',
method: 'get',
params: {},
});
}

+ 68
- 15
web_src/vuepages/langs/config/en-US.js View File

@@ -177,21 +177,21 @@ const en = {
Activated: 'Activated',
notActive: 'Not active',
},
tranformImageFailed:'Picture desensitization failed',
originPicture:'Origin picture',
desensitizationPicture:'Desensitization picture',
desensitizationObject:'Desensitization object',
example:'Example',
startDesensitization:'Start desensitization',
all:'All',
onlyFace:'Only face',
onlyLicensePlate:'Only license plate',
dragThePictureHere:'Drag the picture here',
or:' or ',
clickUpload:'Click upload',
dataDesensitizationModelExperience:'Data desensitization model experience',
dataDesensitizationModelDesc:'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project',
limitFilesUpload:'Only jpg/jpeg/png files can be uploaded',
tranformImageFailed: 'Picture desensitization failed',
originPicture: 'Origin picture',
desensitizationPicture: 'Desensitization picture',
desensitizationObject: 'Desensitization object',
example: 'Example',
startDesensitization: 'Start desensitization',
all: 'All',
onlyFace: 'Only face',
onlyLicensePlate: 'Only license plate',
dragThePictureHere: 'Drag the picture here',
or: ' or ',
clickUpload: 'Click upload',
dataDesensitizationModelExperience: 'Data desensitization model experience',
dataDesensitizationModelDesc: 'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project',
limitFilesUpload: 'Only jpg/jpeg/png files can be uploaded',
limitSizeUpload: 'The size of the uploaded file cannot exceed 20M!',
notebook: {
createNewNotebook: "Create new notebook debug task",
@@ -285,6 +285,59 @@ const en = {
modelAccessPublic:'Public',
modelAccessPrivate:'Private',
},
repos: {
activeOrganization: 'Active Organization',
activeUsers: 'Active Users',
follow: 'Follow',
unFollow: 'Unfollow',
selectedFields: 'Recommend Repositories',
mostPopular: 'Most Popular',
mostActive: 'Most Active',
newest: 'Newest',
recentlyUpdated: 'Recently Updated',
mostStars: 'Most Stars',
mostForks: 'Most Forks',
mostDatasets: 'Most Datasets',
mostAiTasks: 'Most AI Tasks',
mostModels: 'Most Models',
dataset: 'Datasets',
model: 'Models',
aiTask: 'AI Tasks',
updated: 'Updated',
contributors: 'Contributors',
searchRepositories: 'Search Repositories',
search: 'Search',
allFields: 'All Fields',
preferred: 'Preferred',
openIIncubation: 'OpenI Incubation',
hotPapers: 'Hot Papers',
watch: 'Watch',
star: 'Star',
fork: 'Fork',
noReposfound: 'No matching repositories found.',
},
timeObj: {
ago: '{msg} ago',
from_now: '{msg} from now',
now: 'now',
future: 'future',
'1s': '1 second',
'1m': '1 minute',
'1h': '1 hour',
'1d': '1 day',
'1w': '1 week',
'1mon': '1 month',
'1y': '1 year',
seconds: '{msg} seconds',
minutes: '{msg} minutes',
hours: '{msg} hours',
days: '{msg} days',
weeks: '{msg} weeks',
months: '{msg} months',
years: '{msg} years',
raw_seconds: 'seconds',
raw_minutes: 'minutes',
},
}

export default en;

+ 71
- 4
web_src/vuepages/langs/config/zh-CN.js View File

@@ -220,8 +220,24 @@ const zh = {
graphicMemory: "显存",
memory: "内存",
sharedMemory: "共享内存",
tips:'本次新建的调试任务会放在您名下项目openi-notebook中,如果没有该项目系统会自动新建一个。'
tips: '本次新建的调试任务会放在您名下项目openi-notebook中,如果没有该项目系统会自动新建一个。'
},
tranformImageFailed: '图片脱敏失败',
originPicture: '原始图片',
desensitizationPicture: '脱敏图片',
desensitizationObject: '脱敏对象',
example: '示例',
startDesensitization: '开始处理',
all: '全部',
onlyFace: '仅人脸',
onlyLicensePlate: '仅车牌',
dragThePictureHere: '拖动图片到这里',
or: '或',
clickUpload: '点击上传',
dataDesensitizationModelExperience: '数据脱敏模型体验',
dataDesensitizationModelDesc: '利用人工智能AI技术,把图片中的人脸、车牌号码进行脱敏处理。该模型更多信息请访问项目',
limitFilesUpload: '只能上传 jpg/jpeg/png 格式的文件',
limitSizeUpload: '上传文件大小不能超过 20M !',
modelManage: {
modelManage: '模型管理',
modelName: '模型名称',
@@ -286,8 +302,59 @@ const zh = {
modelAccessPublic:'公开',
modelAccessPrivate:'私有',
},
};


repos: {
activeOrganization: '活跃组织',
activeUsers: '活跃用户',
follow: '关注',
unFollow: '取消关注',
selectedFields: '领域精选',
mostPopular: '近期热门',
mostActive: '近期活跃',
newest: '最近创建',
recentlyUpdated: '最近更新',
mostStars: '点赞最多',
mostForks: '派生最多',
mostDatasets: '数据集最多',
mostAiTasks: 'AI任务最多',
mostModels: '模型最多',
dataset: '数据集',
model: '模型',
aiTask: 'AI任务',
updated: '最后更新于',
contributors: '贡献者',
searchRepositories: '搜项目',
search: '搜索',
allFields: '全部领域',
preferred: '项目优选',
openIIncubation: '启智孵化管道',
hotPapers: '热门论文项目',
watch: '关注',
star: '点赞',
fork: '派生',
noReposfound: '未找到匹配的项目。',
},
timeObj: {
ago: '{msg}前',
from_now: '{msg} 之后',
now: '现在',
future: '将来',
'1s': '1 秒',
'1m': '1 分钟',
'1h': '1 小时',
'1d': '1 天',
'1w': '1 周',
'1mon': '1 个月',
'1y': '1 年',
seconds: '{msg} 秒',
minutes: '{msg} 分钟',
hours: '{msg} 小时',
days: '{msg} 天',
weeks: '{msg} 周',
months: '{msg} 个月',
years: '{msg} 年',
raw_seconds: '秒',
raw_minutes: '分钟',
},
}

export default zh;

+ 123
- 0
web_src/vuepages/pages/repos/components/ActiveOrgs.vue View File

@@ -0,0 +1,123 @@
<template>
<div>
<div class="container">
<div class="title">
<i style="margin-left:10px;margin-right:8px;font-size:20px;" class="ri-blaze-line"></i>
<span>{{ $t('repos.activeOrganization') }}</span>
</div>
<div class="content">
<div class="item" v-for="(item, index) in list" :key="index">
<div class="item-l">
<a class="name" :href="`/${item.Name}`" :title="item.Name">
<img class="avatar" :src="item.RelAvatarLink">
<div class="name-c"><span>{{ item.Name }}</span></div>
</a>
</div>
<div class="item-r">
<i class="ri-user-2-line" style="color:rgb(250, 140, 22);margin-right:4px;"></i>
<span>{{ item.NumMembers }}</span>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import { getActiveOrgs } from '~/apis/modules/repos';

export default {
name: "ActiveOrgs",
props: {},
components: {},
data() {
return {
list: [],
};
},
methods: {},
mounted() {
getActiveOrgs().then(res => {
res = res.data;
if (res.Code == 0) {
this.list = res.Data.Orgs || [];
} else {
this.list = [];
}
}).catch(err => {
console.log(err);
this.list = [];
});
},
};
</script>

<style scoped lang="less">
.title {
height: 43px;
border-color: rgba(16, 16, 16, 0.05);
border-width: 1px 0px;
border-style: solid;
display: flex;
align-items: center;
font-size: 18px;
color: rgba(47, 9, 69, 0.74);
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-1.007%2C%200.0010000000000001494%2C%20-0.000018400023883213844%2C%20-1.007%2C%201.003%2C%200.008)%22%3E%3Cstop%20stop-color%3D%22%23eee9da%22%20stop-opacity%3D%220%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23f3e7f7%22%20stop-opacity%3D%220.26%22%20offset%3D%220.29%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23d0e7ff%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.content {
padding: 6px 0;
}

.item {
display: flex;
align-items: center;
height: 48px;
padding: 0 8px;
font-size: 14px;
color: rgba(16, 16, 16, 1);
}

.item>div {
display: flex;
align-items: center;
}

.item-l {
flex: 1;
overflow: hidden;
a {
display: flex;
align-items: center;
overflow: hidden;
}
}

.item-r {
width: 80px;
justify-content: flex-end;
}

.item .avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 10px;
}

.item .name-c {
flex: 1;
overflow: hidden;
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
}

.item .name {
color: rgba(16, 16, 16, 1);

&:hover {
opacity: 0.8;
}
}
</style>

+ 156
- 0
web_src/vuepages/pages/repos/components/ActiveUsers.vue View File

@@ -0,0 +1,156 @@
<template>
<div>
<div class="container">
<div class="title">
<i style="margin-left:10px;margin-right:8px;font-size:20px;" class="ri-account-pin-circle-line"></i>
<span>{{ $t('repos.activeUsers') }}</span>
</div>
<div class="content">
<div class="item" v-for="(item, index) in list" :key="index">
<div class="item-l">
<a class="name" :href="`/${item.User.Name}`" :title="(item.User.FullName || item.User.Name)">
<img class="avatar" :src="item.User.RelAvatarLink">
<div class="name-c">
{{ item.User.FullName || item.User.Name }}
</div>
</a>
</div>
<div class="item-r">
<template v-if="item.ShowButton">
<a class="op-btn" v-if="!item.Followed" href="javascript:;" @click="following(item, index, true)">
{{ $t('repos.follow') }}</a>
<a class="op-btn" v-if="item.Followed" href="javascript:;" @click="following(item, index, false)">
{{ $t('repos.unFollow') }}</a>
</template>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import { getActiveUsers, followingUsers } from '~/apis/modules/repos';

export default {
name: "ActiveUsers",
props: {},
components: {},
data() {
return {
list: [],
};
},
methods: {
following(userInfo, index, followingOrNot) {
followingUsers(userInfo.User.Name, followingOrNot).then(res => {
if (res.status == 204) { // 成功
userInfo.Followed = !userInfo.Followed;
} else {
console.log(res);
}
}).catch(err => {
if (err.response.status == 401) { // 未登陆
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href)}`;
} else {
console.log(err);
}
});
}
},
mounted() {
getActiveUsers().then(res => {
res = res.data;
if (res.Code == 0) {
this.list = res.Data.Users || [];
} else {
this.list = [];
}
}).catch(err => {
console.log(err);
this.list = [];
});
},
};
</script>

<style scoped lang="less">
.title {
height: 43px;
border-color: rgba(16, 16, 16, 0.05);
border-width: 1px 0px;
border-style: solid;
display: flex;
align-items: center;
font-size: 18px;
color: rgba(47, 9, 69, 0.74);
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-1.007%2C%200.0010000000000001494%2C%20-0.000018400023883213844%2C%20-1.007%2C%201.003%2C%200.008)%22%3E%3Cstop%20stop-color%3D%22%23eee9da%22%20stop-opacity%3D%220%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23f3e7f7%22%20stop-opacity%3D%220.26%22%20offset%3D%220.29%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23d0e7ff%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

.content {
padding: 6px 0;
}

.item {
display: flex;
align-items: center;
height: 48px;
padding: 0 8px;
font-size: 14px;
color: rgba(16, 16, 16, 1);
}

.item>div {
display: flex;
align-items: center;
}

.item-l {
flex: 1;
overflow: hidden;

a {
display: flex;
align-items: center;
overflow: hidden;
}
}

.item-r {
width: 80px;
justify-content: flex-end;
}

.item .avatar {
width: 32px;
height: 32px;
border-radius: 50%;
margin-right: 10px;
}

.item .name-c {
flex: 1;
overflow: hidden;
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
}

.item .name {
color: rgba(16, 16, 16, 1);

&:hover {
opacity: 0.8;
}
}

.item .op-btn {
border-color: rgb(50, 145, 248);
border-width: 1px;
border-style: solid;
color: rgb(50, 145, 248);
font-size: 12px;
padding: 0px 6px;
border-radius: 3px;
}
</style>

+ 151
- 0
web_src/vuepages/pages/repos/components/RecommendRepos.vue View File

@@ -0,0 +1,151 @@
<template>
<div class="repo-selected-bg">
<div class="ui container _repo_container _repo-selected-container" style="padding-top:3rem;padding-bottom:3rem;">
<div class="_repo_title"><span>{{ $t('repos.selectedFields') }}</span></div>
<div class="_repo-selected-list">
<div class="swiper-wrapper" id="_repo-selected"></div>
<div class="swiper-pagination _repo-selected-swiper-pagination"></div>
</div>
</div>
</div>
</template>

<script>
import { getHomePageData } from '~/apis/modules/repos';
import LetterAvatar from '~/utils/letteravatar';

export default {
name: "RecommendRepos",
props: {
static: { type: Boolean, default: false },
staticSwiperData: { type: Array, default: () => [] },
},
components: {},
data() {
return {
swiperHandler: null,
};
},
methods: {
renderRepos(json) {
var selectedRepoEl = document.getElementById("_repo-selected");
var html = "";
if (json != null && json.length > 0) {
var repoMap = {};
for (var i = 0, iLen = json.length; i < iLen; i++) {
var repo = json[i];
var label = repo.Label;
if (repoMap[label]) {
repoMap[label].push(repo);
} else {
repoMap[label] = [repo];
}
}
for (var label in repoMap) {
var repos = repoMap[label];
var labelSearch = repos[0].Label;
html += `<div class="swiper-slide"><div><a style="color:rgb(50, 145, 248);font-size:16px;font-weight:550;" href="/explore/repos?q=&topic=${labelSearch}&sort=hot"># ${label}</a></div>`;
for (var i = 0, iLen = repos.length; i < iLen; i++) {
if (i >= 4) break;
var repo = repos[i];
html += `<div class="ui fluid card">
<div class="content">
${repo["Avatar"] ? `<img style="border-radius: 100%;" class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img style="border-radius: 100%;" class="left floated mini ui image" avatar="${repo["OwnerName"]}">`}
<span class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;" href="javascript:;" title="${repo["Alias"]}">${repo["Alias"]}</span>
<div class="description nowrap-2" style="rgba(136,136,136,1);;font-size:12px;" title="${repo["Description"]}">${repo["Description"]}</div>
</div>
<a style="position:absolute;height:100%;width:100%;" href="/${repo["OwnerName"]}/${repo["Name"]}"></a>
</div>`;
}
html += '</div>'
}
this.swiperHandler = new Swiper("._repo-selected-list", {
slidesPerView: 1,
spaceBetween: 25,
pagination: {
el: "._repo-selected-swiper-pagination",
clickable: true,
},
autoplay: {
delay: 4500,
disableOnInteraction: false,
},
breakpoints: {
768: {
slidesPerView: 3,
},
1024: {
slidesPerView: 4,
},
1200: {
slidesPerView: 4,
},
1600: {
slidesPerView: 4,
}
},
});
selectedRepoEl.innerHTML = html;
this.swiperHandler.updateSlides();
this.swiperHandler.updateProgress();
LetterAvatar.transform();
}
}
},
mounted() {
if (this.static) {
this.renderRepos(this.staticSwiperData);
} else {
getHomePageData().then(res => {
this.renderRepos(res.data.repo);
}).catch(err => {
console.log(err);
});
}
},
};
</script>

<style scoped lang="less">
.repo-selected-bg {
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.11899999999999993%2C%201.217%2C%20-0.24039506172839506%2C%200.11899999999999993%2C%200.269%2C%20-0.22)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.47%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23e5e7eb%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

._repo_title {
font-size: 18px;
color: rgb(16, 16, 16);
text-align: center;
margin-bottom: 1em;
font-weight: bold;
}

._repo-selected-list {
overflow: hidden;
padding: 1em 1em 3em 1em;
text-align: left;
position: relative;
}

/deep/._repo-selected-swiper-pagination .swiper-pagination-bullet {
width: 8px;
height: 8px;
border-radius: 100%;
background: #76cbed;
}

/deep/._repo-selected-swiper-pagination .swiper-pagination-bullet-active {
width: 40px;
border-radius: 4px;
}

/deep/ ._repo-selected-list .card {
border-radius: 6px;
background-color: #FFF;
box-shadow: 0px 5px 10px 0px rgba(105, 192, 255, .3);
border: 1px solid rgba(105, 192, 255, .4);
}

/deep/ ._repo-selected-list .header {
line-height: 40px !important;
}
</style>

+ 115
- 0
web_src/vuepages/pages/repos/components/ReposFilters.vue View File

@@ -0,0 +1,115 @@
<template>
<div>
<div class="item" :class="(focusIndex == index) ? 'item-focus' : ''" v-for="(item, index) in list" :key="item.key">
<a href="javascript:;" @click="changeFilters(item, index)">{{ item.label }}</a>
</div>
</div>
</template>

<script>
export default {
name: "ReposFilters",
props: {
defaultsort: { type: String, default: 'mostpopular' },
},
components: {},
data() {
return {
focusIndex: 0,
list: [{
key: 'mostpopular',
label: this.$t('repos.mostPopular'),
}, {
key: 'mostactive',
label: this.$t('repos.mostActive'),
}, {
key: 'recentupdate',
label: this.$t('repos.recentlyUpdated'),
}, {
key: 'newest',
label: this.$t('repos.newest'),
}, {
key: 'moststars',
label: this.$t('repos.mostStars'),
}, {
key: 'mostforks',
label: this.$t('repos.mostForks'),
}, {
key: 'mostdatasets',
label: this.$t('repos.mostDatasets'),
}, {
key: 'mostaitasks',
label: this.$t('repos.mostAiTasks'),
}, {
key: 'mostmodels',
label: this.$t('repos.mostModels'),
}]
};
},
methods: {
changeFilters(item, index) {
this.focusIndex = index;
this.$emit('change', this.list[this.focusIndex]);
},
setDefaultFilter(sort) {
const index = this.list.findIndex((item) => item.key == sort);
this.focusIndex = index >= 0 ? index : 0;
}
},
mounted() {
},
};
</script>

<style scoped lang="less">
.item {
height: 40px;
border-color: rgba(16, 16, 16, 0.05);
border-width: 0px 0px 1px;
border-style: solid;
color: rgba(16, 16, 16, 0.8);
font-size: 14px;
padding: 0px;
display: flex;
align-items: center;
justify-content: flex-end;
position: relative;
}

.item a {
color: inherit;
height: 100%;
display: flex;
align-items: center;

&:hover {
opacity: 0.8;
}
}

.item-focus {
font-weight: bold;
border-color: rgba(0, 108, 205, 0.3);
color: rgb(50, 145, 248);

a {
cursor: default;

&:hover {
opacity: 1;
}
}

}

.item-focus:before {
content: "";
position: absolute;
width: 7px;
height: 7px;
bottom: -4px;
background: rgb(178, 211, 240);
left: 0;
border-radius: 100%;
}
</style>

+ 316
- 0
web_src/vuepages/pages/repos/components/ReposItem.vue View File

@@ -0,0 +1,316 @@
<template>
<div>
<div class="item">
<div class="item-top">
<img v-if="data.RelAvatarLink" class="avatar" :src="data.RelAvatarLink" />
<img v-else class="avatar" :avatar="data.OwnerName" />
<div class="content">
<div class="title">
<div class="title-l">
<a :href="`/${data.OwnerName}/${data.Name}`" :title="`${data.OwnerName}/${data.Name}`">
<span class="title-1">{{ data.OwnerName }}</span>
<span class="title-1"> / </span>
<span class="title-2" v-html="data.NameShow"></span>
</a>
<i v-if="data.IsArchived" class="archive icon archived-icon"></i>
<svg v-if="data.IsFork" class="svg octicon-repo-forked" width="15" height="15" aria-hidden="true">
<use xlink:href="#octicon-repo-forked"></use>
</svg>
<svg v-if="data.IsMirror" class="svg octicon-repo-clone" width="15" height="15" aria-hidden="true">
<use xlink:href="#octicon-repo-clone"></use>
</svg>
<svg v-if="(data.IsPrivate || data.IsOwnerPrivate)" style="color:#a1882b!important" class="svg octicon-lock" width="15" height="15"
aria-hidden="true">
<use xlink:href="#octicon-lock"></use>
</svg>
</div>
<span class="title-r">
<span class="t-item" :title="$t('repos.watch')">
<i class="ri-eye-line"></i>
<span>{{ data.NumWatches }}</span>
</span>
<span class="t-item" :title="$t('repos.star')">
<i class="ri-star-line"></i>
<span>{{ data.NumStars }}</span>
</span>
<span class="t-item" :title="$t('repos.fork')">
<svg class="svg octicon-repo-forked" width="13" height="13" aria-hidden="true">
<use xlink:href="#octicon-repo-forked"></use>
</svg>
<span>{{ data.NumForks }}</span></span>
</span>
</div>
<div class="descr" v-show="data.DescriptionShow" v-html="data.DescriptionShow"></div>
<div class="tags" v-show="data.Topics && data.Topics.length">
<a v-for="(item, index) in data.TopicsShow" :key="index" class="tag"
:class="(item.topic.toLocaleLowerCase() == topic.toLocaleLowerCase() ? 'tag-focus' : '')"
:href="`/explore/repos?q=&topic=${item.topic}&sort=hot`" v-html="item.topicShow"></a>
</div>
<div class="repo-datas" v-show="(data.DatasetCnt > 0) || (data.ModelCnt > 0) || (data.AiTaskCnt > 0)">
<span class="repo-datas-item" v-show="(data.DatasetCnt > 0)">
<i class="ri-stack-line"></i>
<span class="label">{{ $t('repos.dataset') }}:</span>
<span class="value">{{ data.DatasetCnt }}</span>
</span>
<span class="repo-datas-item" v-show="(data.ModelCnt > 0)">
<i class="ri-send-plane-2-line"></i>
<span class="label">{{ $t('repos.model') }}:</span>
<span class="value">{{ data.ModelCnt }}</span>
</span>
<span class="repo-datas-item" v-show="(data.AiTaskCnt > 0)">
<i class="ri-order-play-line"></i>
<span class="label">{{ $t('repos.aiTask') }}:</span>
<span class="value">{{ data.AiTaskCnt }}</span>
</span>
</div>
</div>
</div>
<div class="item-bottom">
<div>
<span>{{ $t('repos.updated') }}</span>
<el-tooltip effect="dark" :content="dateFormat(data.UpdatedUnix)" placement="top-start">
<span>{{ calcFromNow(data.UpdatedUnix) }}</span>
</el-tooltip>
<span style="margin-left:8px;" v-if="data.PrimaryLanguage"><i class="color-icon"
:style="{ backgroundColor: data.PrimaryLanguage.Color }"></i>{{ data.PrimaryLanguage.Language }}</span>
</div>
<div class="contributors">
<span class="contributors-count" v-show="data.Contributors && data.Contributors.length">
{{ $t('repos.contributors') }}&nbsp;
</span>
<span class="contributors-avatar">
<a :href="item.UserName ? `/${item.UserName}` : `mailto:${item.Email}`" class="avatar-c"
v-for="(item, index) in data.Contributors" :key="index">
<img class="avatar" v-show="item.UserName" :src="item.RelAvatarLink">
<span class="avatar" v-show="!item.UserName" :style="{ backgroundColor: item.bgColor }">
{{ item.Email[0].toLocaleUpperCase() }}</span>
</a>
</span>
</div>
</div>
</div>
</div>
</template>

<script>
import relativeTime from 'dayjs/plugin/relativeTime';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/en';
import dayjs from 'dayjs';
import { lang } from '~/langs';
import { timeSinceUnix } from '~/utils';

dayjs.locale(lang == 'zh-CN' ? 'zh-cn' : 'en');
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);

export default {
name: "ReposItem",
props: {
data: { type: Object, default: () => ({}) },
topic: { type: String, default: '' }
},
components: {},
data() {
return {
contributors: [],
};
},
methods: {
calcFromNow(unix) {
// return dayjs(unix * 1000).fromNow();
return timeSinceUnix(unix, Date.now() / 1000);
},
dateFormat(unix) {
return lang == 'zh-CN' ? dayjs(unix * 1000).format('YYYY年MM月DD日 HH时mm分ss秒') :
dayjs(unix * 1000).format('ddd, D MMM YYYY HH:mm:ss [CST]');
}
},
mounted() { },
};
</script>

<style scoped lang="less">
.item {
width: 100%;
border-color: rgba(157, 197, 226, 0.4);
border-width: 1px;
border-style: solid;
box-shadow: rgb(157 197 226 / 20%) 0px 5px 10px 0px;
border-radius: 15px;
font-size: 14px;
padding: 20px 26px 10px 26px;
margin-bottom: 40px;
}

.item-top {
display: flex;
}

.item-top .avatar {
width: 38px;
height: 38px;
margin-right: 10px;
border-radius: 100%;
}

.content {
flex: 1;
overflow: hidden;
}

.content .title {
display: flex;
align-items: center;
height: 30px;
margin: 4px 0 8px;
}

.content .title-l {
flex: 1;
overflow: hidden;
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
}

.content .title-1 {
font-size: 18px;
color: rgba(16, 16, 16, 0.6);
}

.content .title-2 {
font-size: 18px;
color: rgba(16, 16, 16, 1);
font-weight: bold;
margin-right: 3px;
}

.content .title-r {
display: flex;
align-items: center;
font-weight: 400;
font-size: 12px;
color: rgba(26, 40, 51, 1);
justify-content: flex-end;
}

.content .t-item {
margin-left: 12px;
display: flex;
align-items: center;
}

.content .t-item i {
margin-right: 4px;
}

.content .descr {
font-weight: 300;
font-size: 14px;
color: rgba(16, 16, 16, 0.8);
margin-bottom: 16px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 6;
max-height: 120px;
white-space: break-spaces;
}

.content .tags {
margin-bottom: 16px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.content .tag {
color: rgba(16, 16, 16, 0.8);
border-radius: 4px;
font-size: 14px;
background: rgba(232, 232, 232, 0.6);
padding: 2px 6px;
margin-right: 8px;

&.tag-focus {
color: red;
}
}

.content .repo-datas {
display: flex;
align-items: center;
margin-top: 20px;
margin-bottom: 10px;
}

.content .repo-datas-item {
display: flex;
align-items: center;
margin-right: 24px;
}

.content .repo-datas-item i {
color: rgba(2, 107, 251, 0.54);
margin-right: 4px;
font-size: 16px;
}

.content .repo-datas-item .label {
color: rgba(2, 107, 251, 0.54);
margin-right: 4px;
}

.content .repo-datas-item .value {
font-weight: bold;
}

.item-bottom {
display: flex;
align-items: center;
justify-content: space-between;
border-top: 1px solid rgba(157, 197, 226, 0.2);
margin-top: 10px;
padding-top: 10px;
font-size: 12px;
color: rgba(16, 16, 16, 0.6);
}

.item-bottom .contributors {
display: flex;
align-items: center;
}

.item-bottom .contributors-avatar {
display: flex;
align-items: center;
margin-left: 16px;

.avatar-c {

img[src=""],
img:not([src]) {
// opacity: 0;
}
}
}

.item-bottom .avatar {
display: block;
width: 25px;
height: 25px;
margin-left: -6px;
border-radius: 100%;
border: 1px solid white;
font-size: 16px;
line-height: 24px;
text-align: center;
color: white;
background-color: white;
font-weight: bold;
}
</style>

+ 161
- 0
web_src/vuepages/pages/repos/components/ReposList.vue View File

@@ -0,0 +1,161 @@
<template>
<div class="list-container">
<div style="min-height:540px;" v-loading="loading">
<div class="repos-item-container" v-for="(item, index) in list" :key="item.ID">
<ReposItem :data="item" :topic="topic"></ReposItem>
</div>
<div v-show="(!list.length && !loading)" class="repos-no-data">{{ $t('repos.noReposfound') }}</div>
</div>
<div class="center">
<el-pagination ref="paginationRef" background @current-change="currentChange" @size-change="sizeChange"
:current-page.sync="iPage" :page-sizes="iPageSizes" :page-size.sync="iPageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</template>

<script>
import ReposItem from '../components/ReposItem.vue';
import { getReposListData } from '~/apis/modules/repos';
import LetterAvatar from '~/utils/letteravatar';

export default {
name: "ReposList",
props: {
q: { type: String, default: '' },
sort: { type: String, default: 'mostpopular' },
topic: { type: String, default: '' },
page: { type: Number, default: 1 },
pageSize: { type: Number, default: 15 },
pageSizes: { type: Array, default: () => [15, 30, 50] }
},
components: { ReposItem },
data() {
return {
loading: false,
list: [],
iPageSizes: [15, 30, 50],
iPageSize: 15,
iPage: 1,
total: 0,
};
},
methods: {
getListData() {
this.loading = true;
getReposListData({
q: this.q || '',
topic: this.topic || '',
sort: this.sort || 'mostpopular',
pageSize: this.iPageSize || 15,
page: this.iPage || 1,
}).then(res => {
res = res.data;
this.loading = false;
if (res.Code == 0) {
const list = res.Data.Repos || [];
this.list = list.map((item) => {
item.Contributors = (item.Contributors || []).map((_item) => {
return {
..._item,
bgColor: this.randomColor(_item.Email[0].toLocaleUpperCase()),
}
});
const contributors = item.Contributors || [];
return {
...item,
NameShow: this.handlerSearchStr(item.Alias, this.q),
DescriptionShow: this.handlerSearchStr(item.Description, this.q),
TopicsShow: (item.Topics || []).map((_item) => {
return {
topic: _item,
topicShow: this.handlerSearchStr(_item, this.q)
}
}),
}
});
this.total = res.Data.Total;
this.iPage = this.iPage;
this.iPageSize = this.iPageSize;
this.$nextTick(() => {
LetterAvatar.transform();
});
} else {
this.list = [];
this.total = 0;
this.iPage = this.iPage;
this.iPageSize = this.iPageSize;
}
}).catch(err => {
console.log(err);
this.loading = false;
this.list = [];
this.total = 0;
this.iPage = this.iPage;
this.iPageSize = this.iPageSize;
});
},
search() {
this.getListData();
},
currentChange(page) {
this.iPage = page;
this.$emit('current-change', {
page: this.iPage,
pageSize: this.iPageSize,
});
},
sizeChange(pageSize) {
this.iPageSize = pageSize;
this.$emit('size-change', {
page: this.iPage,
pageSize: this.iPageSize,
});
},
handlerSearchStr(oStr, searchKey) {
if (!searchKey) return oStr;
return oStr.replace(new RegExp(`(${searchKey})`, 'ig'), `<font color="red">$1</font>`);
},
randomColor(t) {
const tIndex = t.charCodeAt(0);
const colorList = ["#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad",
"#2c3e50", "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"];
return colorList[tIndex % colorList.length];
}
},
watch: {
page: {
handler(val) {
this.iPage = val;
},
immediate: true,
},
pageSize: {
handler(val) {
this.iPageSize = val;
},
immediate: true,
}
},
mounted() { },
};
</script>

<style scoped lang="less">
.list-container {
margin-left: 12px;
margin-right: 12px;
}

.center {
text-align: center;
}

.repos-no-data {
height: 60px;
display: flex;
justify-content: center;
align-items: center;
}
</style>

+ 255
- 0
web_src/vuepages/pages/repos/components/SearchBar.vue View File

@@ -0,0 +1,255 @@
<template>
<div>
<div class="_repo_search">
<div class="_repo_search_input_c">
<div class="_repo_search_input">
<input type="text" v-model="searchInputValue" :placeholder="$t('repos.searchRepositories')" autocomplete="off"
@keyup.enter="search" />
</div>
<div class="_repo_search_btn" @click="search">
<svg xmlns="http://www.w3.org/2000/svg"
class="styles__StyledSVGIconPathComponent-sc-16fsqc8-0 kdvdTY svg-icon-path-icon fill" viewBox="0 0 32 32"
width="24" height="24">
<defs data-reactroot="">
<linearGradient id="ilac9fwnydq3lcx1,1,rs,1,f0dwf0xj,ezu9f0dw,f000,00lwrsktrs,rs1bhv8urs" x1="0" x2="100%"
y1="0" y2="0"
gradientTransform="matrix(-0.7069999999999999, -0.707, 0.707, -0.7069999999999999, 16, 38.624)"
gradientUnits="userSpaceOnUse">
<stop stop-color="#c9ffbf" stop-opacity="1" offset="0"></stop>
<stop stop-color="#0ca451" stop-opacity="1" offset="1"></stop>
</linearGradient>
</defs>
<g>
<path fill="url(#ilac9fwnydq3lcx1,1,rs,1,f0dwf0xj,ezu9f0dw,f000,00lwrsktrs,rs1bhv8urs)"
d="M14.667 2.667c6.624 0 12 5.376 12 12s-5.376 12-12 12-12-5.376-12-12 5.376-12 12-12zM14.667 24c5.156 0 9.333-4.177 9.333-9.333 0-5.157-4.177-9.333-9.333-9.333-5.157 0-9.333 4.176-9.333 9.333 0 5.156 4.176 9.333 9.333 9.333zM25.98 24.095l3.772 3.771-1.887 1.887-3.771-3.772 1.885-1.885z">
</path>
</g>
</svg>
<span style="margin-left:10px;">{{ $t('repos.search') }}</span>
</div>
</div>
<div class="_repo_search_label_c">
<div class="_repo_search_label">
<a v-if="type == 'square'" :href="`/explore/repos?q=${searchInputValue.trim()}&topic=${item.v}&sort=${sort}`"
:style="{ backgroundColor: topicColors[index % topicColors.length] }" v-for="(item, index) in topics"
:key="index">{{ item.v }}</a>
<a v-if="type == 'search'" href="javascript:;" @click="changeTopic({ k: '', v: '' })"
style="font-weight:bold;"
:style="{ backgroundColor: selectTopic == '' ? selectedColor : defaultColor, color: selectTopic == '' ? 'white' : '#40485b' }">{{
$t('repos.allFields')
}}</a>
<a v-if="type == 'search'" href="javascript:;" @click="changeTopic(item)"
:style="{ backgroundColor: selectTopic.toLocaleLowerCase() == item.k ? selectedColor : defaultColor, color: selectTopic.toLocaleLowerCase() == item.k ? 'white' : '#40485b' }"
v-for="(item, index) in topics" :key="index">{{ item.v }}</a>
</div>
</div>
</div>
</div>
</template>

<script>
import { getPromoteData } from '~/apis/modules/common';
const COLOR_LIST = [
'rgb(255, 104, 104)',
'rgb(22, 132, 252)',
'rgb(2, 202, 253)',
'rgb(164, 145, 215)',
'rgb(232, 64, 247)',
'rgb(245, 182, 110)',
'rgb(54, 187, 166)',
'rgb(123, 50, 178)'
];
export default {
name: "SearchBar",
props: {
type: { type: String, default: 'square' }, // square|search
searchValue: { type: String, default: '' },
topic: { type: String, default: '' },
sort: { type: String, default: '' },
static: { type: Boolean, default: false },
staticTopicsData: { type: String, default: '[]' },
},
components: {},
data() {
return {
searchInputValue: '',
topicColors: COLOR_LIST,
defaultColor: '#F6F6F6',
selectedColor: '',
selectTopic: '',
topicOri: [],
topics: [],
};
},
methods: {
setDefaultSearch(params) {
this.searchInputValue = params.q || '';
this.selectTopic = params.topic || '';
this.selectTopic && this.changeTopic({
k: this.selectTopic.toLocaleLowerCase(),
v: this.selectTopic,
}, true);
},
changeTopic(topicItem, noSearch) {
const index_ori = this.topicOri.findIndex((item) => {
return item.k == this.selectTopic.toLocaleLowerCase();
});
if (index_ori < 0 && this.selectTopic) {
const index = this.topics.findIndex((item) => {
return item.k == this.selectTopic.toLocaleLowerCase();
});
if (index > -1) {
this.topics.splice(index, 1);
}
}
this.selectTopic = topicItem.v;
if (this.selectTopic && this.topics.indexOf(this.selectTopic) < 0) {
const index = this.topics.findIndex(item => {
return item.k == this.selectTopic.toLocaleLowerCase();
})
if (index < 0) {
this.topics.push({
k: this.selectTopic.toLocaleLowerCase(),
v: this.selectTopic,
});
}
}
!noSearch && this.search();
},
handlerTopicsData(data) {
try {
const topicsData = JSON.parse(data);
const topics = topicsData.map((item) => {
return {
k: item.trim().toLocaleLowerCase(),
v: item.trim(),
}
});
this.topicOri = JSON.parse(JSON.stringify(topics));
this.topics = topics;
const selectTopic_key = this.selectTopic.toLocaleLowerCase();
if (selectTopic_key) {
const index = this.topics.findIndex((item) => {
return item.k == selectTopic_key;
});
if (index < 0) {
this.topics.push({
k: this.selectTopic.toLocaleLowerCase(),
v: this.selectTopic,
});
}
}
} catch (err) {
console.log(err);
}
},
search() {
this.searchInputValue = this.searchInputValue.trim();
if (this.type == 'square') {
window.location.href = `/explore/repos?q=${this.searchInputValue}&sort=${this.sort}&topic=${this.selectTopic}`;
} else {
this.$emit('change', {
q: this.searchInputValue,
topic: this.selectTopic,
});
}
}
},
mounted() {
if (this.static) {
try {
this.handlerTopicsData(this.staticTopicsData);
} catch (err) {
console.log(err);
}
} else {
getPromoteData('/repos/recommend_topics').then(res => {
const data = res.data;
this.handlerTopicsData(data);
}).catch(err => {
console.log(err);
this.handlerTopicsData('[]');
});
}
},
};
</script>

<style scoped lang="less">
._repo_search {
margin: 54px 0;
}

._repo_search_input_c {
margin: 0 0 35px;
display: flex;
justify-content: center;
align-items: center;
}

._repo_search_input {
display: flex;
align-items: center;
width: 437px;
height: 41px;
border-color: rgba(47, 9, 69, 0.64);
border-width: 1px;
border-style: solid;
color: rgba(16, 16, 16, 0.5);
border-radius: 20px;
font-size: 14px;
padding: 20px;
text-align: left;
line-height: 20px;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736765e-17%2C%20-0.9999999999999999%2C%200.008802475794500678%2C%206.123233995736765e-17%2C%201%2C%201.003)%22%3E%3Cstop%20stop-color%3D%22%23eeeade%22%20stop-opacity%3D%220.2%22%20offset%3D%220.76%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ece0e9%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}

._repo_search_input input {
width: 100%;
height: 30px;
border: none;
background: transparent;
outline: none;
}

._repo_search_btn {
margin-left: 10px;
width: 110px;
height: 40px;
border-style: none;
border-color: unset;
color: rgb(255, 255, 255);
border-radius: 21px;
font-size: 14px;
text-align: center;
font-weight: normal;
font-style: normal;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.17728531855955676%2C%206.123233995736766e-17%2C%201%2C%200)%22%3E%3Cstop%20stop-color%3D%22%232f0945%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23341a7b%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}

._repo_search_label_c {
display: flex;
justify-content: center;
}

._repo_search_label {
display: flex;
justify-content: center;
flex-wrap: wrap;
max-width: 1200px;
}

._repo_search_label a {
background-color: rgb(2, 202, 253);
color: rgb(255, 255, 255);
border-radius: 5px;
margin: 0 5px 10px 5px;
padding: 5px 10px;
cursor: pointer;
font-size: 12px;
}
</style>

+ 363
- 0
web_src/vuepages/pages/repos/components/SquareTop.vue View File

@@ -0,0 +1,363 @@
<template>
<div class="ui _repo_container_bg">
<div class="ui container _repo_container _content_container">
<div class="_repo_top_left">
<a :href="bannerData[0] ? bannerData[0].url : ''">
<div class="_repo_top_left_img">
<img :src="bannerData[0] ? bannerData[0].src : ''">
</div>
</a>
</div>
<div class="_repo_top_middle">
<div class="_repo_top_middle_header">
<div class="_repo_top_mid_item" :class="(tabIndex == index) ? '_foucs' : ''" v-for="(item, index) in tabs"
:key="index" @click="changeTab(item, index)">
<i :class="item.icon"></i>
<span>{{ item.label }}</span>
</div>
</div>
<div class="_repo_top_middle_content">
<div class="_repo_top_mid_repo_list">
<div class="swiper-wrapper" id="_repo_top_mid_repo"></div>
<div class="swiper-pagination _repo_top-swiper-pagination"></div>
</div>
</div>
</div>
<div class="_repo_top_right">
<a :href="bannerData[1] ? bannerData[1].url : ''">
<div class="_repo_top_right_img">
<img :src="bannerData[1] ? bannerData[1].src : ''">
</div>
</a>
</div>
</div>
</div>
</template>

<script>
import { getPromoteData } from '~/apis/modules/common';
import { getReposSquareTabData } from '~/apis/modules/repos';
import LetterAvatar from '~/utils/letteravatar';

export default {
name: "SquareTop",
props: {
static: { type: Boolean, default: false },
staticSwiperData: { type: Array, default: () => [] },
staticBannerData: { type: String, default: '[]' },
},
components: {},
data() {
return {
swiperHandler: null,
tabIndex: 0,
tabs: [{
key: 'preferred',
icon: 'ri-fire-line',
label: this.$t('repos.preferred'),
}, {
key: 'incubation',
icon: 'ri-award-line',
label: this.$t('repos.openIIncubation'),
}, {
key: 'hot-paper',
icon: 'ri-file-damage-line',
label: this.$t('repos.hotPapers'),
}],
bannerData: [],
};
},
methods: {
initSwiper() {
this.swiperHandler = new Swiper("._repo_top_mid_repo_list", {
slidesPerView: 1,
spaceBetween: 20,
pagination: {
el: "._repo_top-swiper-pagination",
clickable: true,
},
autoplay: {
delay: 4500,
disableOnInteraction: false,
},
breakpoints: {
768: {
slidesPerView: 2,
},
1024: {
slidesPerView: 2,
},
1200: {
slidesPerView: 3,
},
},
});
},
renderSwiper(data) {
const swiperEl = document.getElementById("_repo_top_mid_repo");
let html = '';
const width = swiperEl.parentNode.clientWidth;
for (let i = 0, iLen = data.length; i < iLen; i++) {
html += `<div class="swiper-slide">`;
for (let j = i; j < i + 2; j++) {
let dataJ = data[j];
if (dataJ === undefined) break;
html += `<div class="_repo_sw_card"><div style="display:flex;">
${dataJ["RelAvatarLink"] ? `<img style="border-radius:100%;width:35px;height:35px;margin-bottom:0.6em;" class="left floated mini ui image" src="${dataJ["RelAvatarLink"]}">`
: `<img style="border-radius:100%;width:35px;height:35px;margin-bottom:0.6em;" class="left floated mini ui image" avatar="${dataJ["OwnerName"]}">`}
<span class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;height:35px;line-height:35px;" href="javascript:;" title="${dataJ["Alias"]}">${dataJ["Alias"]}</span>
</div><div class="_repo_sw_card_descr _repo_nowrap_line_3" title="${dataJ.Description}">${dataJ.Description}</div>
<a href="/${dataJ.OwnerName}/${dataJ.Name}" style="position:absolute;width:100%;height:100%;top:0;left:0;"></a>
</div>`;
}
html += `</div>`;
i++;
}
this.swiperHandler.removeAllSlides();
swiperEl.innerHTML = html;
this.swiperHandler.updateSlides();
this.swiperHandler.updateProgress();
LetterAvatar.transform();
},
getBannerData() {
getPromoteData('/repos/square_banner').then(res => {
const data = res.data;
try {
const list = JSON.parse(data);
this.bannerData = list;
} catch (err) {
console.log(err);
}
}).catch(err => {
this.bannerData = [];
console.log(err);
});
},
getTabData() {
getReposSquareTabData(this.tabs[this.tabIndex].key).then(res => {
res = res.data;
if (res.Code == 0) {
const data = res.Data.Repos || [];
this.renderSwiper(data);
} else {
this.renderSwiper([]);
}
}).catch(err => {
console.log(err);
this.renderSwiper([]);
});
},
changeTab(item, index) {
this.tabIndex = index;
this.getTabData();
},
},
mounted() {
this.initSwiper();
if (this.static) {
try {
this.bannerData = JSON.parse(this.staticBannerData);
} catch (err) {
console.log(err);
}
this.renderSwiper(this.staticSwiperData);
} else {
this.getBannerData();
this.getTabData();
}
},
};
</script>

<style scoped lang="less">
._repo_container_bg {
width: 100%;
height: 450px;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.4999999999999999%2C%20-0.8660000000000001%2C%200.07722000385802469%2C%200.4999999999999999%2C%20-0.183%2C%200.683)%22%3E%3Cstop%20stop-color%3D%22%239aceec%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23eeeeee%22%20stop-opacity%3D%221%22%20offset%3D%220.99%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
position: relative;
}

._content_container {
display: flex !important;
margin: 0 auto !important;
height: 100% !important;
}

._repo_top_left {
width: 243px;
height: 100%;
position: relative;
}

._repo_top_left_img {
width: 100%;
height: 346px;
position: absolute;
bottom: 0;
border-top-left-radius: 10px;
overflow: hidden;
}

._repo_top_left_img img {
height: 100%;
width: 100%;
}

._content_container img[src=""],
img:not([src]) {
opacity: 0;
}

._repo_top_right {
width: 243px;
height: 100%;
position: relative;
}

._repo_top_right_img {
width: 100%;
height: 346px;
position: absolute;
bottom: 0;
border-top-right-radius: 10px;
overflow: hidden;
}

._repo_top_right_img img {
height: 100%;
width: 100%;
}

._repo_top_middle {
flex: 1;
height: 100%;
position: relative;

}

._repo_top_middle_header {
width: 100%;
height: 42px;
position: absolute;
bottom: 346px;
display: flex;
align-items: center;
background: raba(0, 0, 255, 0.3);
padding: 0 20px;
}

._repo_top_mid_item {
padding: 0px 16px;
font-size: 18px;
color: rgba(47, 9, 69, 0.64);
height: 100%;
display: flex;
align-items: center;
cursor: pointer;
box-sizing: border-box;
border-bottom: 3px solid transparent;
}

._repo_top_mid_item i {
margin-right: 5px;
}

._repo_top_mid_item._foucs {
background-color: rgba(255, 255, 255, 0.3);
color: rgb(16, 16, 16);
cursor: default;
border-bottom: 3px solid rgba(16, 16, 16, 0.8);
}

._repo_top_middle_content {
width: 100%;
height: 346px;
position: absolute;
bottom: 0;
background: rgba(255, 255, 255, 0.3);
padding: 20px 20px;
overflow: hidden;
}

._repo_top_mid_repo_list {
overflow: hidden;
}

/deep/._repo_sw_card {
height: 128px;
border-color: rgb(255, 255, 255);
border-width: 1px;
border-style: solid;
font-size: 14px;
padding: 12px;
background: rgba(255, 255, 255, 0.6);
margin-bottom: 20px;
box-sizing: border-box;
position: relative;
}

/deep/._repo_nowrap {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

/deep/._repo_nowrap_line_3 {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
font-size: 12px;
color: rgb(136, 136, 136);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
max-height: 65px;
white-space: break-spaces;
}

/deep/._repo_sw_card a {
color: inherit;
}

/deep/._repo_sw_card_title {
font-weight: 700;
font-size: 14px;
color: rgba(26, 40, 51, 1);
margin-bottom: 10px;
}

/deep/._repo_sw_card_descr {
font-size: 12px;
color: rgba(80, 85, 89, 1);
margin-bottom: 10px;
min-height: 42px;
}

/deep/._repo_sw_card_label {
color: rgb(26, 40, 51);
font-size: 14px;
display: flex;
width: 100%;
position: relative;
}

/deep/._repo_sw_card_label span {
border-radius: 4px;
background: rgba(232, 232, 232, 0.6);
padding: 0px 6px 2px;
margin-right: 10px;
max-width: 50%;
}

/deep/._repo_top_mid_repo_list .swiper-pagination-bullet {
width: 8px;
height: 8px;
border-radius: 100%;
background: #76cbed;
}

/deep/._repo_top_mid_repo_list .swiper-pagination-bullet-active {
width: 40px;
border-radius: 4px;
}
</style>

+ 123
- 0
web_src/vuepages/pages/repos/search/index.vue View File

@@ -0,0 +1,123 @@
<template>
<div>
<div class="ui container">
<SearchBar ref="searchBarRef" type="search" :static="true" :staticTopicsData="staticSquareTopics"
:sort="reposListSortType" :searchValue="reposListQurey" :topic="reposListTopic" @change="searchBarChange">
</SearchBar>
</div>
<div class="ui container">
<div class="ui grid">
<div class="computer only ui two wide computer column">
<ReposFilters ref="reposFiltersRef" @change="filtersChange"></ReposFilters>
</div>
<div class="ui sixteen wide mobile twelve wide tablet ten wide computer column">
<ReposList ref="reposListRef" :sort="reposListSortType" :q="reposListQurey" :topic="reposListTopic"
:page="page" :pageSize="pageSize" :pageSizes="pageSizes" @current-change="currentChange"
@size-change="sizeChange">
</ReposList>
</div>
<div class="computer only ui four wide computer column">
<div>
<ActiveUsers></ActiveUsers>
</div>
<div class="active-org-c">
<ActiveOrgs></ActiveOrgs>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import SearchBar from '../components/SearchBar.vue';
import ReposFilters from '../components/ReposFilters.vue';
import ReposList from '../components/ReposList.vue';
import ActiveUsers from '../components/ActiveUsers.vue';
import ActiveOrgs from '../components/ActiveOrgs.vue';
import { getUrlSearchParams } from '~/utils';

const staticSquareTopics = JSON.stringify(window.staticSquareTopics || []);

export default {
data() {
return {
reposListSortType: 'mostpopular',
reposListQurey: '',
reposListTopic: '',

page: 1,
pageSize: 15,
pageSizes: [15, 30, 50],

staticSquareTopics: staticSquareTopics,
};
},
components: {
SearchBar,
ReposFilters,
ReposList,
ActiveUsers,
ActiveOrgs,
},
methods: {
filtersChange(condition) {
this.page = 1;
this.reposListSortType = condition.key;
this.search();
},
searchBarChange(params) {
this.page = 1;
this.reposListQurey = params.q || '';
this.reposListTopic = params.topic || '';
this.search();
},
currentChange({ page, pageSize }) {
this.page = page;
this.search();
},
sizeChange({ page, pageSize }) {
this.page = 1;
this.pageSize = pageSize;
this.search();
},
search() {
window.location.href = `/explore/repos?q=${this.reposListQurey.trim()}&sort=${this.reposListSortType}&topic=${this.reposListTopic.trim()}&page=${this.page}&pageSize=${this.pageSize}`;
}
},
beforeMount() {
const urlParams = getUrlSearchParams();
this.reposListQurey = urlParams.q || '';
this.reposListTopic = urlParams.topic || '';
this.reposListSortType = urlParams.sort || 'mostpopular';
this.page = Number(urlParams.page) || 1;
this.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15;
},
mounted() {
this.$nextTick(() => {
this.$refs.reposFiltersRef.setDefaultFilter(this.reposListSortType);
this.$refs.searchBarRef.setDefaultSearch({
q: this.reposListQurey,
topic: this.reposListTopic,
});
this.$refs.reposListRef.search();
});
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
window.location.reload();
}
}, false);
},
beforeDestroy() { },
};
</script>

<style scoped lang="less">
.recommend-repos-c {
margin: 0 0 54px;
}

.active-org-c {
margin-top: 32px;
}
</style>

+ 17
- 0
web_src/vuepages/pages/repos/search/vp-repos-search.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './index.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 146
- 0
web_src/vuepages/pages/repos/square/index.vue View File

@@ -0,0 +1,146 @@
<template>
<div>
<div>
<SquareTop :static="true" :staticBannerData="staticSquareBanners" :staticSwiperData="staticSquarePreferredRepos">
</SquareTop>
</div>
<div class="ui container">
<SearchBar :static="true" :staticTopicsData="staticSquareTopics" ref="searchBarRef" type="square" :sort="``"
:searchValue="reposListQurey" :topic="``" @change="searchBarChange"></SearchBar>
</div>
<div class="recommend-repos-c">
<RecommendRepos :static="true" :staticSwiperData="staticSquareRecommendRepos"></RecommendRepos>
<a name="search"></a>
</div>
<div class="ui container">
<div class="ui grid">
<div class="computer only ui two wide computer column">
<ReposFilters ref="reposFiltersRef" @change="filtersChange"></ReposFilters>
</div>
<div class="ui sixteen wide mobile twelve wide tablet ten wide computer column">
<ReposList ref="reposListRef" :sort="reposListSortType" :q="reposListQurey" :topic="reposListTopic"
:page="page" :pageSize="pageSize" :pageSizes="pageSizes" @current-change="currentChange"
@size-change="sizeChange">
</ReposList>
</div>
<div class="computer only ui four wide computer column">
<div>
<ActiveUsers></ActiveUsers>
</div>
<div class="active-org-c">
<ActiveOrgs></ActiveOrgs>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import SquareTop from '../components/SquareTop.vue';
import SearchBar from '../components/SearchBar.vue';
import RecommendRepos from '../components/RecommendRepos.vue';
import ReposFilters from '../components/ReposFilters.vue';
import ReposList from '../components/ReposList.vue';
import ActiveUsers from '../components/ActiveUsers.vue';
import ActiveOrgs from '../components/ActiveOrgs.vue';
import { getUrlSearchParams } from '~/utils';

const staticSquareBanners = JSON.stringify(window.staticSquareBanners || []);
const staticSquarePreferredRepos = window.staticSquarePreferredRepos || [];
const staticSquareTopics = JSON.stringify(window.staticSquareTopics || []);
const staticSquareRecommendRepos = window.staticSquareRecommendRepos || [];

export default {
data() {
return {
reposListSortType: 'mostpopular',
reposListQurey: '',
reposListTopic: '',

page: 1,
pageSize: 15,
pageSizes: [15, 30, 50],

staticSquareBanners: staticSquareBanners,
staticSquarePreferredRepos: staticSquarePreferredRepos,
staticSquareTopics: staticSquareTopics,
staticSquareRecommendRepos: staticSquareRecommendRepos,
};
},
components: {
SquareTop,
SearchBar,
RecommendRepos,
ReposFilters,
ReposList,
ActiveUsers,
ActiveOrgs,
},
methods: {
filtersChange(condition) {
this.page = 1;
this.reposListSortType = condition.key;
this.search();
},
searchBarChange(params) {
this.page = 1;
this.reposListQurey = params.q || '';
this.reposListTopic = params.topic || '';
this.search();
},
currentChange({ page, pageSize }) {
this.page = page;
this.search();
},
sizeChange({ page, pageSize }) {
this.page = 1;
this.pageSize = pageSize;
this.search();
},
search() {
window.location.href = `/explore/repos/square?q=${this.reposListQurey.trim()}&sort=${this.reposListSortType}&topic=${this.reposListTopic.trim()}&page=${this.page}&pageSize=${this.pageSize}`;
}
},
beforeMount() {
const urlParams = getUrlSearchParams();
this.reposListQurey = urlParams.q || '';
this.reposListTopic = urlParams.topic || '';
this.reposListSortType = urlParams.sort || 'mostpopular';
this.page = Number(urlParams.page) || 1;
this.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15;
},
mounted() {
this.$nextTick(() => {
this.$refs.reposFiltersRef.setDefaultFilter(this.reposListSortType);
this.$refs.searchBarRef.setDefaultSearch({
q: this.reposListQurey,
topic: this.reposListTopic,
});
const urlParams = getUrlSearchParams();
const page = Number(urlParams.page) || 1;
const reposListSortType = urlParams.sort;
if (page != 1 || reposListSortType) {
window.location.href = '#search';
}
this.$refs.reposListRef.search();
});
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
window.location.reload();
}
}, false);
},
beforeDestroy() { },
};
</script>

<style scoped lang="less">
.recommend-repos-c {
margin: 0 0 54px;
}

.active-org-c {
margin-top: 32px;
}
</style>

+ 17
- 0
web_src/vuepages/pages/repos/square/vp-repos-square.js View File

@@ -0,0 +1,17 @@
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import localeEn from 'element-ui/lib/locale/lang/en';
import localeZh from 'element-ui/lib/locale/lang/zh-CN';
import { i18n, lang } from '~/langs';
import App from './index.vue';

Vue.use(ElementUI, {
locale: lang === 'zh-CN' ? localeZh : localeEn,
size: 'small',
});

new Vue({
i18n,
render: (h) => h(App),
}).$mount('#__vue-root');

+ 89
- 1
web_src/vuepages/utils/index.js View File

@@ -27,7 +27,7 @@ export const transFileSize = (srcSize) => {
srcSize = parseFloat(srcSize);
const index = Math.floor(Math.log(srcSize) / Math.log(1024));
const size = (srcSize / Math.pow(1024, index)).toFixed(2);
return size + ' ' + unitArr[index];
return size + ' ' + unitArr[index];
};

export const renderSpecStr = (spec, showPoint) => {
@@ -39,3 +39,91 @@ export const renderSpecStr = (spec, showPoint) => {
var specStr = `${ngpu}, CPU: ${spec.CpuCores}, ${gpuMemStr}${i18n.t('resourcesManagement.mem')}: ${spec.MemGiB}GB${sharedMemStr}${pointStr}`;
return specStr;
};

const Minute = 60;
const Hour = 60 * Minute;
const Day = 24 * Hour;
const Week = 7 * Day;
const Month = 30 * Day;
const Year = 12 * Month;

const computeTimeDiff = (diff) => {
let diffStr = '';
switch (true) {
case diff <= 0:
diff = 0;
diffStr = i18n.t('timeObj.now');
break;
case diff < 2:
diff = 0;
diffStr = i18n.t('timeObj.1s');
break;
case diff < 1 * Minute:
diffStr = i18n.t('timeObj.seconds', { msg: Math.floor(diff) });
diff = 0;
break;
case diff < 2 * Minute:
diff -= 1 * Minute;
diffStr = i18n.t('timeObj.1m');
break;
case diff < 1 * Hour:
diffStr = i18n.t('timeObj.minutes', { msg: Math.floor(diff / Minute) });
diff -= diff / Minute * Minute;
break;
case diff < 2 * Hour:
diff -= 1 * Hour;
diffStr = i18n.t('timeObj.1h');
break;
case diff < 1 * Day:
diffStr = i18n.t('timeObj.hours', { msg: Math.floor(diff / Hour) });
diff -= diff / Hour * Hour;
break;
case diff < 2 * Day:
diff -= 1 * Day;
diffStr = i18n.t('timeObj.1d');
break;
case diff < 1 * Week:
diffStr = i18n.t('timeObj.days', { msg: Math.floor(diff / Day) });
diff -= diff / Day * Day;
break;
case diff < 2 * Week:
diff -= 1 * Week;
diffStr = i18n.t('timeObj.1w');
break;
case diff < 1 * Month:
diffStr = i18n.t('timeObj.weeks', { msg: Math.floor(diff / Week) });
diff -= diff / Week * Week;
break;
case diff < 2 * Month:
diff -= 1 * Month;
diffStr = i18n.t('timeObj.1mon');
break;
case diff < 1 * Year:
diffStr = i18n.t('timeObj.months', { msg: Math.floor(diff / Month) });
diff -= diff / Month * Month;
break;
case diff < 2 * Year:
diff -= 1 * Year;
diffStr = i18n.t('timeObj.1y');
break;
default:
diffStr = i18n.t('timeObj.years', { msg: Math.floor(diff / Year) });
diff -= (diff / Year) * Year;
break;
}
return { diff, diffStr };
};

export const timeSinceUnix = (then, now) => {
let lbl = 'timeObj.ago';
let diff = now - then;
if (then > now) {
lbl = 'timeObj.from_now';
diff = then - now;
}
if (diff <= 0) {
return i18n.t('timeObj.now');
}
const out = computeTimeDiff(diff);
return i18n.t(lbl, { msg: out.diffStr });
};

+ 75
- 0
web_src/vuepages/utils/letteravatar.js View File

@@ -0,0 +1,75 @@

function LetterAvatar(name, size, color) {
name = name || "";
size = size || 60;
var colours = [
"#1abc9c",
"#2ecc71",
"#3498db",
"#9b59b6",
"#34495e",
"#16a085",
"#27ae60",
"#2980b9",
"#8e44ad",
"#2c3e50",
"#f1c40f",
"#e67e22",
"#e74c3c",
"#00bcd4",
"#95a5a6",
"#f39c12",
"#d35400",
"#c0392b",
"#bdc3c7",
"#7f8c8d",
],
nameSplit = String(name).split(" "),
initials,
charIndex,
colourIndex,
canvas,
context,
dataURI;
if (nameSplit.length == 1) {
initials = nameSplit[0] ? nameSplit[0].charAt(0) : "?";
} else {
initials = nameSplit[0].charAt(0) + nameSplit[1].charAt(0);
}
let initials1 = initials.toUpperCase();
if (window.devicePixelRatio) {
size = size * window.devicePixelRatio;
}

charIndex = (initials == "?" ? 72 : initials.charCodeAt(0)) - 64;
colourIndex = charIndex % 20;
canvas = document.createElement("canvas");
canvas.width = size;
canvas.height = size;
context = canvas.getContext("2d");

context.fillStyle = color ? color : colours[colourIndex - 1];
context.fillRect(0, 0, canvas.width, canvas.height);
context.font = Math.round(canvas.width / 2) + "px 'Microsoft Yahei'";
context.textAlign = "center";
context.fillStyle = "#FFF";
context.fillText(initials1, size / 2, size / 1.5);
dataURI = canvas.toDataURL();
canvas = null;
return dataURI;
}

LetterAvatar.transform = function () {
Array.prototype.forEach.call(
document.querySelectorAll("img[avatar]"),
function (img, name, color) {
name = img.getAttribute("avatar");
color = img.getAttribute("color");
img.src = LetterAvatar(name, img.getAttribute("width"), color);
img.removeAttribute("avatar");
img.setAttribute("alt", name);
}
);
};

export default LetterAvatar;

+ 2
- 2
webpack.config.js View File

@@ -22,8 +22,8 @@ for (const path of glob('web_src/less/themes/*.less')) {

const standalone = {};
const stadalonePaths = [
...glob('web_src/js/standalone/*.js'),
...glob('web_src/less/standalone/*.less'),
...glob('web_src/js/standalone/**/*.js'),
...glob('web_src/less/standalone/**/*.less'),
];
for (const path of stadalonePaths) {
standalone[parse(path).name] = [path];


+ 2
- 2
webpack_pro.config.js View File

@@ -22,8 +22,8 @@ for (const path of glob('web_src/less/themes/*.less')) {

const standalone = {};
const stadalonePaths = [
...glob('web_src/js/standalone/*.js'),
...glob('web_src/less/standalone/*.less'),
...glob('web_src/js/standalone/**/*.js'),
...glob('web_src/less/standalone/**/*.less'),
];
for (const path of stadalonePaths) {
standalone[parse(path).name] = [path];


Loading…
Cancel
Save