| @@ -167,6 +167,9 @@ func init() { | |||||
| new(Badge), | new(Badge), | ||||
| new(BadgeUser), | new(BadgeUser), | ||||
| new(BadgeUserLog), | new(BadgeUserLog), | ||||
| new(TechConvergeBaseInfo), | |||||
| new(RepoConvergeInfo), | |||||
| new(UserRole), | |||||
| ) | ) | ||||
| tablesStatistic = append(tablesStatistic, | tablesStatistic = append(tablesStatistic, | ||||
| @@ -192,6 +192,8 @@ type SearchRepoOptions struct { | |||||
| // False -> include just no courses | // False -> include just no courses | ||||
| Course util.OptionalBool | Course util.OptionalBool | ||||
| OnlySearchPrivate bool | OnlySearchPrivate bool | ||||
| RepoIds []int64 | |||||
| } | } | ||||
| //SearchOrderBy is used to sort the result | //SearchOrderBy is used to sort the result | ||||
| @@ -206,6 +208,7 @@ type FindReposResponse struct { | |||||
| Page int | Page int | ||||
| PageSize int | PageSize int | ||||
| Total int64 | Total int64 | ||||
| RepoIds []int64 | |||||
| } | } | ||||
| // Strings for sorting result | // Strings for sorting result | ||||
| @@ -281,6 +284,33 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { | |||||
| if opts.StarredByID > 0 { | if opts.StarredByID > 0 { | ||||
| cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) | cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID}))) | ||||
| } | } | ||||
| if len(opts.RepoIds) > 0 { | |||||
| const MaxINItems = 1000 | |||||
| if len(opts.RepoIds) <= MaxINItems { | |||||
| cond = cond.And(builder.In("id", opts.RepoIds)) | |||||
| } else { | |||||
| repoIdsMap := make(map[int][]int64, 0) | |||||
| i := 0 | |||||
| for j := 0; j < len(opts.RepoIds); j++ { | |||||
| if _, ok := repoIdsMap[i]; !ok { | |||||
| repoIdsMap[i] = make([]int64, 0) | |||||
| } | |||||
| repoIdsMap[i] = append(repoIdsMap[i], opts.RepoIds[j]) | |||||
| if (j+1)%MaxINItems == 0 { | |||||
| i += 1 | |||||
| } | |||||
| } | |||||
| subCond := builder.NewCond() | |||||
| for _, repoSplit := range repoIdsMap { | |||||
| subCond = subCond.Or(builder.In("id", repoSplit)) | |||||
| } | |||||
| cond = cond.And(subCond) | |||||
| } | |||||
| } | |||||
| // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate | // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate | ||||
| if opts.OwnerID > 0 { | if opts.OwnerID > 0 { | ||||
| @@ -0,0 +1,71 @@ | |||||
| package models | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/timeutil" | |||||
| "fmt" | |||||
| ) | |||||
| type RoleType string | |||||
| const ( | |||||
| TechProgramAdmin RoleType = "TechProgramAdmin" | |||||
| ) | |||||
| type Role struct { | |||||
| Type RoleType | |||||
| Name string | |||||
| Description string | |||||
| } | |||||
| type UserRole struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| UserId int64 `xorm:"INDEX UNIQUE(uq_user_role)"` | |||||
| RoleType RoleType `xorm:"UNIQUE(uq_user_role)"` | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||||
| } | |||||
| func NewUserRole(r UserRole) (int64, error) { | |||||
| return x.Insert(&r) | |||||
| } | |||||
| func DeleteUserRole(roleType RoleType, userId int64) (int64, error) { | |||||
| return x.Where("role_type = ? and user_id = ?", roleType, userId).Delete(&UserRole{}) | |||||
| } | |||||
| func GetUserRoleByUserAndRole(userId int64, roleType RoleType) (*UserRole, error) { | |||||
| r := &UserRole{} | |||||
| has, err := x.Where("role_type = ? and user_id = ?", roleType, userId).Get(r) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| return nil, ErrRecordNotExist{} | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func GetRoleByCode(code string) (*Role, error) { | |||||
| r := &Role{} | |||||
| has, err := x.Where("code = ?", code).Get(r) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| return nil, ErrRecordNotExist{} | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| type ErrRoleNotExists struct { | |||||
| } | |||||
| func IsErrRoleNotExists(err error) bool { | |||||
| _, ok := err.(ErrRoleNotExists) | |||||
| return ok | |||||
| } | |||||
| func (err ErrRoleNotExists) Error() string { | |||||
| return fmt.Sprintf("role is not exists") | |||||
| } | |||||
| type OperateRoleReq struct { | |||||
| UserName string `json:"user_name" binding:"Required"` | |||||
| RoleType RoleType `json:"role_type" binding:"Required"` | |||||
| } | |||||
| @@ -0,0 +1,661 @@ | |||||
| package models | |||||
| import ( | |||||
| "strconv" | |||||
| "strings" | |||||
| "time" | |||||
| "code.gitea.io/gitea/modules/timeutil" | |||||
| "xorm.io/builder" | |||||
| ) | |||||
| const ( | |||||
| TechShow = 1 | |||||
| TechHide = 2 | |||||
| TechMigrating = 3 | |||||
| TechMigrateFailed = 4 | |||||
| TechNotExist = 5 | |||||
| ) | |||||
| const DefaultTechApprovedStatus = TechShow | |||||
| type TechConvergeBaseInfo struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| ProjectNumber string `xorm:"UNIQUE NOT NULL"` //项目立项编号 | |||||
| ProjectName string //科技项目名称 | |||||
| Institution string //项目承担单位 | |||||
| ApplyYear int //申报年度 | |||||
| Province string //所属省(省市) | |||||
| Category string //单位性质 | |||||
| Recommend string //推荐单位 | |||||
| Owner string //项目负责人 | |||||
| Phone string //负责人电话 | |||||
| Email string //负责人邮箱 | |||||
| Contact string //项目联系人 | |||||
| ContactPhone string //联系人电话 | |||||
| ContactEmail string //联系人邮箱 | |||||
| ExecuteMonth int //执行周期(月) | |||||
| ExecuteStartYear int //执行开始年份 | |||||
| ExecuteEndYear int //执行结束年份 | |||||
| ExecutePeriod string //执行期限 | |||||
| Type string //项目类型 | |||||
| StartUp string //启动会时间 | |||||
| NumberTopic int | |||||
| Topic1 string | |||||
| Topic2 string | |||||
| Topic3 string | |||||
| Topic4 string | |||||
| Topic5 string | |||||
| Topic6 string | |||||
| Topic7 string | |||||
| AllInstitution string `xorm:"TEXT"` | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||||
| } | |||||
| func (t *TechConvergeBaseInfo) Brief() *TechConvergeBrief { | |||||
| return &TechConvergeBrief{ | |||||
| ProjectNumber: t.ProjectNumber, | |||||
| ProjectName: t.ProjectName, | |||||
| Institution: t.Institution, | |||||
| AllInstitution: t.AllInstitution, | |||||
| } | |||||
| } | |||||
| func (t *TechConvergeBaseInfo) IsValidInstitution(institution string) bool { | |||||
| if t.AllInstitution == "" && t.Institution == "" { | |||||
| return false | |||||
| } | |||||
| allInstitution := make([]string, 0) | |||||
| if t.AllInstitution != "" { | |||||
| allInstitution = strings.Split(t.AllInstitution, ",") | |||||
| } | |||||
| if t.Institution != "" { | |||||
| allInstitution = append(allInstitution, t.Institution) | |||||
| } | |||||
| newInstitution := strings.Split(institution, ",") | |||||
| total := len(newInstitution) | |||||
| matched := 0 | |||||
| for _, n := range newInstitution { | |||||
| for _, s := range allInstitution { | |||||
| if s == n { | |||||
| matched++ | |||||
| break | |||||
| } | |||||
| } | |||||
| } | |||||
| if matched == total { | |||||
| return true | |||||
| } | |||||
| return false | |||||
| } | |||||
| type RepoConvergeInfo struct { | |||||
| ID int64 `xorm:"pk autoincr"` | |||||
| RepoID int64 | |||||
| Url string | |||||
| BaseInfoID int64 | |||||
| Institution string | |||||
| UID int64 | |||||
| Status int | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||||
| User *User `xorm:"-"` | |||||
| Repo *Repository `xorm:"-"` | |||||
| BaseInfo *TechConvergeBaseInfo `xorm:"-"` | |||||
| } | |||||
| func (r *RepoConvergeInfo) InsertOrUpdate() error { | |||||
| if r.ID != 0 { | |||||
| _, err := x.ID(r.ID).Update(r) | |||||
| return err | |||||
| } else { | |||||
| _, err := x.InsertOne(r) | |||||
| return err | |||||
| } | |||||
| } | |||||
| func GetTechConvergeBaseInfoByProjectNumber(projectNumber string) (*TechConvergeBaseInfo, error) { | |||||
| tb := &TechConvergeBaseInfo{ProjectNumber: projectNumber} | |||||
| return getTechConvergeBaseInfo(tb) | |||||
| } | |||||
| func GetTechConvergeBaseInfoById(id int64) (*TechConvergeBaseInfo, error) { | |||||
| tb := &TechConvergeBaseInfo{ID: id} | |||||
| return getTechConvergeBaseInfo(tb) | |||||
| } | |||||
| func (baseInfo *TechConvergeBaseInfo) InsertOrUpdate() error { | |||||
| if baseInfo.ID != 0 { | |||||
| _, err := x.ID(baseInfo.ID).Update(baseInfo) | |||||
| return err | |||||
| } else { | |||||
| _, err := x.InsertOne(baseInfo) | |||||
| return err | |||||
| } | |||||
| } | |||||
| type ErrTechConvergeBaseInfoNotExist struct { | |||||
| ID string | |||||
| } | |||||
| func (err ErrTechConvergeBaseInfoNotExist) Error() string { | |||||
| return "tech.tech_not_exist" | |||||
| } | |||||
| func IsErrTechConvergeBaseInfoNotExist(err error) bool { | |||||
| _, ok := err.(ErrTechConvergeBaseInfoNotExist) | |||||
| return ok | |||||
| } | |||||
| func getTechConvergeBaseInfo(tb *TechConvergeBaseInfo) (*TechConvergeBaseInfo, error) { | |||||
| has, err := x.Get(tb) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| if tb.ProjectNumber != "" { | |||||
| return nil, ErrTechConvergeBaseInfoNotExist{tb.ProjectNumber} | |||||
| } else { | |||||
| return nil, ErrTechConvergeBaseInfoNotExist{strconv.FormatInt(tb.ID, 10)} | |||||
| } | |||||
| } | |||||
| return tb, nil | |||||
| } | |||||
| func GetProjectNames() []string { | |||||
| var names []string | |||||
| x.Table("tech_converge_base_info").Distinct("project_name").Find(&names) | |||||
| return names | |||||
| } | |||||
| func GetIdByProjectName(name string) []string { | |||||
| var ids []int64 | |||||
| x.Table("tech_converge_base_info").Cols("id").Where("project_name=?", name).Find(&ids) | |||||
| idStrs := make([]string, 0, len(ids)) | |||||
| for _, id := range ids { | |||||
| idStrs = append(idStrs, strconv.FormatInt(id, 10)) | |||||
| } | |||||
| return idStrs | |||||
| } | |||||
| func GetSummitRepoIds() []int64 { | |||||
| var ids []int64 | |||||
| x.Table("repo_converge_info").Cols("repo_id").Find(&ids) | |||||
| return ids | |||||
| } | |||||
| func GetTechRepoTopics(limit int) []string { | |||||
| repoIds := GetSummitRepoIds() | |||||
| if len(repoIds) == 0 { | |||||
| return []string{} | |||||
| } | |||||
| //select name, repo_count from topic a, repo_topic b | |||||
| //where a.id=b.topic_id and repo_id in (1,3) order by repo_count desc | |||||
| inCondition := "repo_id in (" | |||||
| const MaxINItems = 1000 | |||||
| for i := 0; i < len(repoIds); i++ { | |||||
| if i == len(repoIds)-1 { | |||||
| inCondition += strconv.FormatInt(repoIds[i], 10) | |||||
| } else if (i+1)%MaxINItems == 0 { | |||||
| inCondition += strconv.FormatInt(repoIds[i], 10) + ") or repo_id in (" | |||||
| } else { | |||||
| inCondition += strconv.FormatInt(repoIds[i], 10) + "," | |||||
| } | |||||
| } | |||||
| inCondition += ")" | |||||
| sql := "select name, repo_count from topic a, repo_topic b where a.id=b.topic_id and (" + inCondition + ") order by repo_count desc" | |||||
| if limit > 0 { | |||||
| sql += "limit " + strconv.Itoa(limit) | |||||
| } | |||||
| result, err := x.QueryString(sql) | |||||
| if err != nil { | |||||
| return []string{} | |||||
| } | |||||
| var topics []string | |||||
| for _, record := range result { | |||||
| topics = append(topics, record["name"]) | |||||
| } | |||||
| return topics | |||||
| } | |||||
| func GetProjectTypes() []string { | |||||
| sql := "SELECT COUNT(id) AS theCount, type from tech_converge_base_info GROUP BY type ORDER BY theCount DESC" | |||||
| result, err := x.QueryString(sql) | |||||
| if err != nil { | |||||
| return []string{} | |||||
| } | |||||
| var projectTypes []string | |||||
| for _, record := range result { | |||||
| projectTypes = append(projectTypes, record["type"]) | |||||
| } | |||||
| return projectTypes | |||||
| } | |||||
| func GetApplyExecuteYears() ([]int, []int) { | |||||
| apply, executeStart, executeEnd := GetYearInfos() | |||||
| applyEnd := time.Now().Year() | |||||
| var applyArray []int | |||||
| var executeArray []int | |||||
| for i := apply; i <= applyEnd; i++ { | |||||
| applyArray = append(applyArray, i) | |||||
| } | |||||
| for i := executeStart; i <= executeEnd; i++ { | |||||
| executeArray = append(executeArray, i) | |||||
| } | |||||
| return applyArray, executeArray | |||||
| } | |||||
| func GetYearInfos() (int, int, int) { | |||||
| sql := "select min(apply_year) as apply_year,min(CASE WHEN execute_start_year != 0 THEN execute_start_year END) as execute_start_year,max(execute_end_year) as execute_end_year from tech_converge_base_info" | |||||
| result, err := x.QueryString(sql) | |||||
| if err != nil { | |||||
| return 2018, 2019, 2024 | |||||
| } | |||||
| for _, record := range result { | |||||
| apply, _ := strconv.Atoi(record["apply_year"]) | |||||
| executeStart, _ := strconv.Atoi(record["execute_start_year"]) | |||||
| executeEnd, _ := strconv.Atoi(record["execute_end_year"]) | |||||
| return apply, executeStart, executeEnd | |||||
| } | |||||
| return 2018, 2019, 2024 | |||||
| } | |||||
| func GetAllInstitutions() []string { | |||||
| var names []string | |||||
| x.Table("tech_converge_base_info").Cols("all_institution").Find(&names) | |||||
| var allNames []string | |||||
| for _, name := range names { | |||||
| singleNames := strings.Split(name, ",") | |||||
| for _, singleName := range singleNames { | |||||
| if singleName != "" { | |||||
| if !contains(allNames, singleName) { | |||||
| allNames = append(allNames, singleName) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| return allNames | |||||
| } | |||||
| func contains(s []string, e string) bool { | |||||
| for _, a := range s { | |||||
| if a == e { | |||||
| return true | |||||
| } | |||||
| } | |||||
| return false | |||||
| } | |||||
| type SearchTechOpt struct { | |||||
| Q string //科技项目名称 | |||||
| ProjectType string | |||||
| Institution string | |||||
| ApplyYear int | |||||
| ExecuteYear int | |||||
| OrderBy string | |||||
| ListOptions | |||||
| } | |||||
| type SearchRepoOpt struct { | |||||
| Q string //项目名称,简介 | |||||
| ProjectName string | |||||
| Topic string | |||||
| Institution string | |||||
| OrderBy string | |||||
| ListOptions | |||||
| } | |||||
| type SearchUserRepoOpt struct { | |||||
| User *User | |||||
| ListOptions | |||||
| } | |||||
| type TechRepoInfoUser struct { | |||||
| ID int64 `json:"id"` | |||||
| ProjectNumber string `json:"no"` | |||||
| ProjectName string `json:"name"` | |||||
| Institution string `json:"institution"` | |||||
| AllInstitution string `json:"all_institution"` | |||||
| Url string `json:"url"` | |||||
| RepoName string `json:"repo_name"` | |||||
| RepoOwnerName string `json:"repo_owner_name"` | |||||
| ContributionInstitution string `json:"contribution_institution"` | |||||
| UserName string `json:"user_name"` | |||||
| Status int `json:"status"` | |||||
| CreatedUnix timeutil.TimeStamp `json:"created_unix"` | |||||
| UpdatedUnix timeutil.TimeStamp `json:"updated_unix"` | |||||
| } | |||||
| type TechRepoInfoAdmin struct { | |||||
| ID int64 `json:"id"` | |||||
| Url string `json:"url"` | |||||
| ContributionInstitution string `json:"contribution_institution"` | |||||
| Status int `json:"status"` | |||||
| ProjectNumber string `json:"no"` | |||||
| ProjectName string `json:"name"` | |||||
| ApplyYear int `json:"apply_year"` | |||||
| Institution string `json:"institution"` | |||||
| Province string `json:"province"` | |||||
| Category string `json:"category"` | |||||
| Recommend string `json:"recommend"` | |||||
| Owner string `json:"owner"` | |||||
| Phone string `json:"phone"` | |||||
| Email string `json:"email"` | |||||
| Contact string `json:"contact"` | |||||
| ContactPhone string `json:"contact_phone"` | |||||
| ContactEmail string `json:"contact_email"` | |||||
| ExecuteMonth int `json:"execute_month"` | |||||
| ExecutePeriod string `json:"execute_period"` | |||||
| Type string `json:"type"` | |||||
| StartUp string `json:"start_up"` | |||||
| NumberTopic int `json:"number_topic"` | |||||
| Topic1 string `json:"topic1"` | |||||
| Topic2 string `json:"topic2"` | |||||
| Topic3 string `json:"topic3"` | |||||
| Topic4 string `json:"topic4"` | |||||
| Topic5 string `json:"topic5"` | |||||
| AllInstitution string `json:"all_institution"` | |||||
| RepoName string `json:"repo_name"` | |||||
| RepoOwnerName string `json:"repo_owner_name"` | |||||
| UserName string `json:"user_name"` | |||||
| CreatedUnix timeutil.TimeStamp `json:"created_unix"` | |||||
| UpdatedUnix timeutil.TimeStamp `json:"updated_unix"` | |||||
| } | |||||
| type RepoWithInstitution struct { | |||||
| ID int64 `json:"id"` | |||||
| OwnerID int64 `json:"owner_id"` | |||||
| OwnerName string `json:"owner_name"` | |||||
| Name string `json:"name"` | |||||
| Alias string `json:"alias"` | |||||
| Topics []string `json:"topics"` | |||||
| Description string `json:"description"` | |||||
| Institution string `json:"institution"` | |||||
| RelAvatarLink string `json:"rel_avatar_link"` | |||||
| UpdatedUnix timeutil.TimeStamp `json:"updated_unix"` | |||||
| } | |||||
| type TechRepoInfo struct { | |||||
| ID int64 `json:"id"` | |||||
| ProjectName string `json:"project_name"` | |||||
| Institution string `json:"institution"` | |||||
| Type string `json:"type"` | |||||
| ApplyYear int `json:"apply_year"` | |||||
| ExecutePeriod string `json:"execute_period"` | |||||
| AllInstitution string `json:"all_institution"` | |||||
| RepoCount int `json:"repo_numer"` | |||||
| Repos []*RepoWithInstitution | |||||
| } | |||||
| func ShowTechRepo(ids []int64, show bool) error { | |||||
| status := TechShow | |||||
| if !show { | |||||
| status = TechHide | |||||
| } | |||||
| idStrs := make([]string, 0, len(ids)) | |||||
| for _, id := range ids { | |||||
| idStrs = append(idStrs, strconv.FormatInt(id, 10)) | |||||
| } | |||||
| sql := "update repo_converge_info set status=? where id in (" + strings.Join(idStrs, ",") + ") and (status=" + strconv.Itoa(TechShow) + " or status=" + strconv.Itoa(TechHide) + ")" | |||||
| _, err := x.Exec(sql, status) | |||||
| return err | |||||
| } | |||||
| func GetTechRepoInfoForUser(opts *SearchUserRepoOpt) ([]*RepoConvergeInfo, int64, error) { | |||||
| cond := buildTechRepoForUserCondition(opts) | |||||
| total, err := x.Where(cond).Count(new(RepoConvergeInfo)) | |||||
| if err != nil { | |||||
| return nil, 0, err | |||||
| } | |||||
| repoConvergeInfos := make([]*RepoConvergeInfo, 0) | |||||
| err = x.Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Desc("status").Find(&repoConvergeInfos) | |||||
| if err != nil { | |||||
| return nil, 0, err | |||||
| } | |||||
| loadAttributes(repoConvergeInfos) | |||||
| return repoConvergeInfos, total, nil | |||||
| } | |||||
| func loadAttributes(infos []*RepoConvergeInfo) { | |||||
| for _, info := range infos { | |||||
| info.User, _ = GetUserByID(info.UID) | |||||
| info.Repo, _ = GetRepositoryByID(info.RepoID) | |||||
| info.BaseInfo, _ = GetTechConvergeBaseInfoById(info.BaseInfoID) | |||||
| } | |||||
| } | |||||
| func buildTechRepoForUserCondition(opts *SearchUserRepoOpt) builder.Cond { | |||||
| var cond = builder.NewCond() | |||||
| if opts.User != nil { | |||||
| cond = cond.And(builder.Eq{"uid": opts.User.ID}) | |||||
| } | |||||
| return cond | |||||
| } | |||||
| func GetAvailableRepoConvergeInfo(opt *SearchRepoOpt) ([]*RepoConvergeInfo, error) { | |||||
| repos := make([]*RepoConvergeInfo, 0) | |||||
| err := x.Table("repo_converge_info").Where(buildRepoFilterCond(opt)).Find(&repos) | |||||
| return repos, err | |||||
| } | |||||
| func buildRepoFilterCond(opt *SearchRepoOpt) string { | |||||
| sql := "status=" + strconv.Itoa(TechShow) | |||||
| if opt.Institution != "" { | |||||
| sql += " and (institution like '%" + opt.Institution + ",%'" + " or institution like '%," + opt.Institution + "%'" + " or institution = '" + opt.Institution + "')" | |||||
| } | |||||
| if opt.ProjectName != "" { | |||||
| baseInfoIds := GetIdByProjectName(opt.ProjectName) | |||||
| if len(baseInfoIds) > 0 { | |||||
| sql += " and base_info_id in (" + strings.Join(baseInfoIds, ",") + ")" | |||||
| } | |||||
| } | |||||
| return sql | |||||
| } | |||||
| func SearchTechRepoInfo(opt *SearchTechOpt) ([]*TechRepoInfo, int64, error) { | |||||
| sql := `select a.*,COALESCE(b.count,0) as repo_count, COALESCE(b.max,0) as max from tech_converge_base_info a left join | |||||
| (select base_info_id,count(id),max(updated_unix) from repo_converge_info where status=` + strconv.Itoa(TechShow) + ` GROUP BY base_info_id ) b | |||||
| on a.id=b.base_info_id` | |||||
| totalSql := "select count(*) from (" + sql + ") c" + buildTechFilterCond(opt) | |||||
| total, err := x.SQL(totalSql).Count(new(TechConvergeBaseInfo)) | |||||
| resultList := make([]*TechRepoInfo, 0) | |||||
| if err != nil { | |||||
| return resultList, total, err | |||||
| } | |||||
| resultSql := "select id,project_name, institution,type,apply_year,execute_period,all_institution,repo_count from (" + | |||||
| sql + ") c " + buildTechFilterCond(opt) + opt.OrderBy + " offset " + strconv.Itoa((opt.Page-1)*opt.PageSize) + " limit " + strconv.Itoa(opt.PageSize) | |||||
| resultMap, err := x.QueryInterface(resultSql) | |||||
| if err == nil { | |||||
| for _, record := range resultMap { | |||||
| resultList = append(resultList, &TechRepoInfo{ | |||||
| ID: record["id"].(int64), | |||||
| ProjectName: record["project_name"].(string), | |||||
| Institution: record["institution"].(string), | |||||
| Type: record["type"].(string), | |||||
| ApplyYear: int(record["apply_year"].(int64)), | |||||
| ExecutePeriod: record["execute_period"].(string), | |||||
| AllInstitution: record["all_institution"].(string), | |||||
| RepoCount: int(record["repo_count"].(int64)), | |||||
| }) | |||||
| } | |||||
| } | |||||
| loadRepoInfoForTech(resultList) | |||||
| return resultList, total, err | |||||
| } | |||||
| func buildTechFilterCond(opt *SearchTechOpt) string { | |||||
| sql := "" | |||||
| if opt.Q != "" { | |||||
| sql += getWherePrefix(sql) + " project_name like '%" + opt.Q + "%'" | |||||
| } | |||||
| if opt.ProjectType != "" { | |||||
| sql += getWherePrefix(sql) + " type ='" + opt.ProjectType + "'" | |||||
| } | |||||
| if opt.ApplyYear != 0 { | |||||
| sql += getWherePrefix(sql) + " apply_year =" + strconv.Itoa(opt.ApplyYear) | |||||
| } | |||||
| if opt.Institution != "" { | |||||
| sql += getWherePrefix(sql) + " (all_institution like '%" + opt.Institution + ",%'" + " or all_institution like '%," + opt.Institution + "%'" + " or all_institution = '" + opt.Institution + "')" | |||||
| } | |||||
| if opt.ExecuteYear != 0 { | |||||
| sql += getWherePrefix(sql) + " execute_start_year <=" + strconv.Itoa(opt.ExecuteYear) + " and execute_end_year >=" + strconv.Itoa(opt.ExecuteYear) | |||||
| } | |||||
| return sql | |||||
| } | |||||
| func getWherePrefix(sql string) string { | |||||
| if sql == "" { | |||||
| return " where " | |||||
| } | |||||
| return " and " | |||||
| } | |||||
| func loadRepoInfoForTech(list []*TechRepoInfo) { | |||||
| for _, techRepo := range list { | |||||
| techRepo.Repos = []*RepoWithInstitution{} | |||||
| if techRepo.RepoCount > 0 { | |||||
| var repoIds []int64 | |||||
| x.Table("repo_converge_info").Cols("repo_id").Where("base_info_id=? and status=?", techRepo.ID, TechShow).Limit(2).Desc("updated_unix").Find(&repoIds) | |||||
| resultMap, err := GetRepositoriesMapByIDs(repoIds) | |||||
| if err == nil { | |||||
| for _, repoId := range repoIds { | |||||
| repo, ok := resultMap[repoId] | |||||
| if ok { | |||||
| techRepo.Repos = append(techRepo.Repos, &RepoWithInstitution{ | |||||
| ID: repo.ID, | |||||
| Institution: techRepo.Institution, | |||||
| OwnerID: repo.OwnerID, | |||||
| OwnerName: repo.OwnerName, | |||||
| Name: repo.Name, | |||||
| Alias: repo.Alias, | |||||
| Topics: repo.Topics, | |||||
| Description: repo.Description, | |||||
| RelAvatarLink: repo.RelAvatarLink(), | |||||
| UpdatedUnix: repo.UpdatedUnix, | |||||
| }) | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| type TechConvergeBrief struct { | |||||
| ProjectNumber string `json:"no"` //项目立项编号 | |||||
| ProjectName string `json:"name"` //科技项目名称 | |||||
| Institution string `json:"institution"` //项目承担单位 | |||||
| AllInstitution string `json:"all_institution"` | |||||
| } | |||||
| type FindTechOpt struct { | |||||
| TechNo string | |||||
| ProjectName string | |||||
| Institution string | |||||
| } | |||||
| func FindTech(opt FindTechOpt) ([]*TechConvergeBaseInfo, error) { | |||||
| var cond = builder.NewCond() | |||||
| if opt.TechNo != "" { | |||||
| cond = cond.And(builder.Like{"project_number", opt.TechNo}) | |||||
| } | |||||
| if opt.ProjectName != "" { | |||||
| cond = cond.And(builder.Like{"project_name", opt.ProjectName}) | |||||
| } | |||||
| if opt.Institution != "" { | |||||
| cond = cond.And(builder.Like{"institution", opt.Institution}.Or(builder.Like{"all_institution", opt.Institution})) | |||||
| } | |||||
| r := make([]*TechConvergeBaseInfo, 0) | |||||
| err := x.Where(cond).OrderBy("updated_unix desc").Find(&r) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func GetTechByTechNo(techNo string) (*TechConvergeBaseInfo, error) { | |||||
| var tech = &TechConvergeBaseInfo{} | |||||
| has, err := x.Where("project_number = ?", techNo).Get(tech) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } else if !has { | |||||
| return nil, ErrTechConvergeBaseInfoNotExist{} | |||||
| } | |||||
| return tech, nil | |||||
| } | |||||
| type GetRepoConvergeOpts struct { | |||||
| RepoId int64 | |||||
| BaseInfoId int64 | |||||
| Status []int | |||||
| } | |||||
| func GetRepoConverge(opts GetRepoConvergeOpts) ([]*RepoConvergeInfo, error) { | |||||
| r := make([]*RepoConvergeInfo, 0) | |||||
| cond := builder.NewCond() | |||||
| if opts.RepoId > 0 { | |||||
| cond = cond.And(builder.Eq{"repo_id": opts.RepoId}) | |||||
| } | |||||
| if opts.BaseInfoId > 0 { | |||||
| cond = cond.And(builder.Eq{"base_info_id": opts.BaseInfoId}) | |||||
| } | |||||
| if len(opts.Status) > 0 { | |||||
| cond = cond.And(builder.In("status", opts.Status)) | |||||
| } | |||||
| err := x.Where(cond).Find(&r) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func UpdateRepoConvergeStatus(id int64, status int) (int64, error) { | |||||
| return x.ID(id).Update(&RepoConvergeInfo{ | |||||
| Status: status, | |||||
| }) | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package structs | |||||
| type NotOpenITechRepo struct { | |||||
| Url string `json:"url" binding:"Required"` | |||||
| TechNo string `json:"no"` | |||||
| Institution string `json:"institution"` | |||||
| UID int64 `json:"uid"` //启智项目uid | |||||
| RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` | |||||
| Alias string `json:"alias" binding:"Required;AlphaDashDotChinese;MaxSize(100)"` | |||||
| Topics []string `json:"topics"` //关键词 | |||||
| Description string `json:"description" binding:"MaxSize(255)"` | |||||
| } | |||||
| type OpenITechRepo struct { | |||||
| Url string `json:"url" binding:"Required"` | |||||
| TechNo string `json:"no"` | |||||
| Institution string `json:"institution"` | |||||
| } | |||||
| @@ -3358,3 +3358,14 @@ get_file_fail= Can not get the image content, please try again later. | |||||
| content_type_unsupported=Allowed image type is jpg, jpeg or png. | content_type_unsupported=Allowed image type is jpg, jpeg or png. | ||||
| process_image_fail=Fail to process image, please try again later. | process_image_fail=Fail to process image, please try again later. | ||||
| [tech] | |||||
| get_file_fail= Can not get the file content, please try again later. | |||||
| content_type_unsupported=The format of the file or file content is wrong. | |||||
| sql_err=Fail to process data, please try again later. | |||||
| show_or_hide_fail=Failed to %s the repo(s). | |||||
| incorrect_openi_format = The OpenI address format is incorrect | |||||
| openi_repo_not_exist = OpenI repository is not exists | |||||
| tech_not_exist = The project approval number does not exist | |||||
| institution_not_valid = The submitted contributing unit is not among the participating units of the technology project | |||||
| repo_converge_exists = The technology project [%s] already has [%s], please do not submit it again | |||||
| to_migrate_repo_exists = The project has been migrated to OpenI, please use the OpenI way to submit | |||||
| @@ -3378,4 +3378,17 @@ new_model_security_evaluation_tips = 模型安全评测只适用于图像分类 | |||||
| get_file_fail= 获取上传文件失败,请稍后再试。 | get_file_fail= 获取上传文件失败,请稍后再试。 | ||||
| content_type_unsupported=请上传jpg、jpeg或png图片。 | content_type_unsupported=请上传jpg、jpeg或png图片。 | ||||
| process_image_fail=图片处理失败,请稍后再试。 | process_image_fail=图片处理失败,请稍后再试。 | ||||
| [tech] | |||||
| get_file_fail= 获取上传文件失败。 | |||||
| content_type_unsupported=上传文件的格式有误。 | |||||
| sql_err=数据处理错误,请稍后再试。 | |||||
| show_or_hide_fail=%s失败。 | |||||
| incorrect_openi_format = 启智项目地址格式错误 | |||||
| openi_repo_not_exist = 启智项目不存在 | |||||
| tech_not_exist = 项目立项编号不存在 | |||||
| institution_not_valid = 当前提交的贡献单位不在该科技项目的参与单位中 | |||||
| repo_converge_exists = 科技项目[%s]中已存在[%s],请勿重复提交 | |||||
| to_migrate_repo_exists = 该项目已迁移到启智,请使用启智社区方式提交申请 | |||||
| @@ -0,0 +1,36 @@ | |||||
| package admin | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/role" | |||||
| "net/http" | |||||
| ) | |||||
| func AddUserRole(ctx *context.APIContext, form models.OperateRoleReq) { | |||||
| user, err := models.GetUserByName(form.UserName) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError("User not exists")) | |||||
| return | |||||
| } | |||||
| err = role.AddUserRole(user.ID, form.RoleType) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterResponseError(err)) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.OuterSuccess()) | |||||
| } | |||||
| func DeleteUserRole(ctx *context.APIContext, form models.OperateRoleReq) { | |||||
| user, err := models.GetUserByName(form.UserName) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError("User not exists")) | |||||
| return | |||||
| } | |||||
| err = role.DeleteUserRole(user.ID, form.RoleType) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterResponseError(err)) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.OuterSuccess()) | |||||
| } | |||||
| @@ -59,9 +59,12 @@ | |||||
| package v1 | package v1 | ||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/services/role" | |||||
| "net/http" | "net/http" | ||||
| "strings" | "strings" | ||||
| "code.gitea.io/gitea/routers/api/v1/tech" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/auth" | "code.gitea.io/gitea/modules/auth" | ||||
| @@ -212,6 +215,16 @@ func reqSiteAdmin() macaron.Handler { | |||||
| } | } | ||||
| } | } | ||||
| // reqTechAdmin user should be the tech program admin | |||||
| func hasRole(roleType models.RoleType) macaron.Handler { | |||||
| return func(ctx *context.Context) { | |||||
| if !ctx.IsSigned || !role.UserHasRole(ctx.User.ID, roleType) { | |||||
| ctx.Error(http.StatusForbidden) | |||||
| return | |||||
| } | |||||
| } | |||||
| } | |||||
| // reqOwner user should be the owner of the repo or site admin. | // reqOwner user should be the owner of the repo or site admin. | ||||
| func reqOwner() macaron.Handler { | func reqOwner() macaron.Handler { | ||||
| return func(ctx *context.Context) { | return func(ctx *context.Context) { | ||||
| @@ -535,6 +548,20 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| }, reqToken()) | }, reqToken()) | ||||
| m.Group("/tech", func() { | |||||
| m.Get("", tech.FindTech) | |||||
| m.Post("/basic", hasRole(models.TechProgramAdmin), tech.ImportBasicInfo) | |||||
| m.Get("/filter", tech.GetFilterInfo) | |||||
| m.Get("/search", tech.SearchTechProjectInfo) | |||||
| m.Get("/repo_search", tech.SearchRepoInfo) | |||||
| m.Get("/admin", hasRole(models.TechProgramAdmin), tech.GetAdminRepoInfo) | |||||
| m.Get("/my", tech.GetMyRepoInfo) | |||||
| m.Post("/admin/:action", hasRole(models.TechProgramAdmin), bind(tech.ActionIDs{}), tech.Action) | |||||
| m.Post("/openi", bind(api.OpenITechRepo{}), tech.CommitOpenIRepo) | |||||
| m.Post("/no_openi", bind(api.NotOpenITechRepo{}), tech.CommitNotOpenIRepo) | |||||
| m.Get("/is_admin", tech.IsAdmin) | |||||
| }, reqToken()) | |||||
| m.Group("/attachments", func() { | m.Group("/attachments", func() { | ||||
| m.Get("/:uuid", repo.GetAttachment) | m.Get("/:uuid", repo.GetAttachment) | ||||
| @@ -1084,6 +1111,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| // Organizations | // Organizations | ||||
| m.Get("/user/orgs", reqToken(), org.ListMyOrgs) | m.Get("/user/orgs", reqToken(), org.ListMyOrgs) | ||||
| m.Get("/user/owners", reqToken(), org.GetMyOwners) | |||||
| m.Get("/users/:username/orgs", org.ListUserOrgs) | m.Get("/users/:username/orgs", org.ListUserOrgs) | ||||
| m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create) | m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create) | ||||
| m.Get("/orgs", org.GetAll) | m.Get("/orgs", org.GetAll) | ||||
| @@ -1164,6 +1192,10 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) | m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) | ||||
| }) | }) | ||||
| }) | }) | ||||
| m.Group("/role", func() { | |||||
| m.Combo("").Post(bind(models.OperateRoleReq{}), admin.AddUserRole). | |||||
| Delete(bind(models.OperateRoleReq{}), admin.DeleteUserRole) | |||||
| }) | |||||
| }, reqToken(), reqSiteAdmin()) | }, reqToken(), reqSiteAdmin()) | ||||
| m.Group("/topics", func() { | m.Group("/topics", func() { | ||||
| @@ -6,6 +6,7 @@ | |||||
| package org | package org | ||||
| import ( | import ( | ||||
| "code.gitea.io/gitea/routers/response" | |||||
| "net/http" | "net/http" | ||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| @@ -278,3 +279,29 @@ func Delete(ctx *context.APIContext) { | |||||
| } | } | ||||
| ctx.Status(http.StatusNoContent) | ctx.Status(http.StatusNoContent) | ||||
| } | } | ||||
| func GetMyOwners(ctx *context.APIContext) { | |||||
| result := make([]*models.User4Front, 0) | |||||
| result = append(result, ctx.User.ToFrontFormat()) | |||||
| orgs, err := models.GetOrgsCanCreateRepoByUserID(ctx.User.ID) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||||
| return | |||||
| } | |||||
| if !ctx.User.IsAdmin { | |||||
| orgsAvailable := []*models.User{} | |||||
| for i := 0; i < len(orgs); i++ { | |||||
| if orgs[i].CanCreateRepo() { | |||||
| orgsAvailable = append(orgsAvailable, orgs[i]) | |||||
| } | |||||
| } | |||||
| orgs = orgsAvailable | |||||
| } | |||||
| for _, o := range orgs { | |||||
| result = append(result, o.ToFrontFormat()) | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(result)) | |||||
| } | |||||
| @@ -6,8 +6,8 @@ package repo | |||||
| import ( | import ( | ||||
| "bytes" | "bytes" | ||||
| "code.gitea.io/gitea/modules/task" | |||||
| "code.gitea.io/gitea/routers/response" | "code.gitea.io/gitea/routers/response" | ||||
| "code.gitea.io/gitea/services/repository" | |||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "net/http" | "net/http" | ||||
| @@ -221,143 +221,9 @@ func handleMigrateError(ctx *context.APIContext, repoOwner *models.User, remoteA | |||||
| func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { | func MigrateSubmit(ctx *context.APIContext, form auth.MigrateRepoForm) { | ||||
| log.Info("receive MigrateSubmit request") | log.Info("receive MigrateSubmit request") | ||||
| ctxUser, bizErr := checkContextUser(ctx, form.UID) | |||||
| if bizErr != nil { | |||||
| ctx.JSON(http.StatusOK, response.ResponseError(bizErr)) | |||||
| return | |||||
| } | |||||
| remoteAddr, err := form.ParseRemoteAddr(ctx.User) | |||||
| err := repository.MigrateSubmit(ctx.User, form) | |||||
| if err != nil { | if err != nil { | ||||
| if models.IsErrInvalidCloneAddr(err) { | |||||
| addrErr := err.(models.ErrInvalidCloneAddr) | |||||
| switch { | |||||
| case addrErr.IsURLError: | |||||
| ctx.JSON(http.StatusOK, response.PARAM_ERROR) | |||||
| case addrErr.IsPermissionDenied: | |||||
| ctx.JSON(http.StatusOK, response.INSUFFICIENT_PERMISSION) | |||||
| case addrErr.IsInvalidPath: | |||||
| ctx.JSON(http.StatusOK, response.PARAM_ERROR) | |||||
| default: | |||||
| ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) | |||||
| } | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, response.SYSTEM_ERROR) | |||||
| } | |||||
| return | |||||
| } | |||||
| var gitServiceType = api.PlainGitService | |||||
| u, err := url.Parse(form.CloneAddr) | |||||
| if err == nil && strings.EqualFold(u.Host, "github.com") { | |||||
| gitServiceType = api.GithubService | |||||
| } | |||||
| var opts = migrations.MigrateOptions{ | |||||
| OriginalURL: form.CloneAddr, | |||||
| GitServiceType: gitServiceType, | |||||
| CloneAddr: remoteAddr, | |||||
| RepoName: form.RepoName, | |||||
| Alias: form.Alias, | |||||
| Description: form.Description, | |||||
| Private: form.Private || setting.Repository.ForcePrivate, | |||||
| Mirror: form.Mirror, | |||||
| AuthUsername: form.AuthUsername, | |||||
| AuthPassword: form.AuthPassword, | |||||
| Wiki: form.Wiki, | |||||
| Issues: form.Issues, | |||||
| Milestones: form.Milestones, | |||||
| Labels: form.Labels, | |||||
| Comments: true, | |||||
| PullRequests: form.PullRequests, | |||||
| Releases: form.Releases, | |||||
| ctx.JSON(http.StatusOK, err) | |||||
| } | } | ||||
| if opts.Mirror { | |||||
| opts.Issues = false | |||||
| opts.Milestones = false | |||||
| opts.Labels = false | |||||
| opts.Comments = false | |||||
| opts.PullRequests = false | |||||
| opts.Releases = false | |||||
| } | |||||
| err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias) | |||||
| if err != nil { | |||||
| handleMigrateError4Api(ctx, ctxUser, remoteAddr, err) | |||||
| return | |||||
| } | |||||
| err = task.MigrateRepository(ctx.User, ctxUser, opts) | |||||
| if err == nil { | |||||
| r := make(map[string]string) | |||||
| r["OpenIUrl"] = strings.TrimSuffix(setting.AppURL, "/") + "/" + ctxUser.Name + "/" + opts.RepoName | |||||
| r["OriginUrl"] = form.CloneAddr | |||||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||||
| return | |||||
| } | |||||
| handleMigrateError4Api(ctx, ctxUser, remoteAddr, err) | |||||
| } | |||||
| func checkContextUser(ctx *context.APIContext, uid int64) (*models.User, *response.BizError) { | |||||
| if uid == ctx.User.ID || uid == 0 { | |||||
| return ctx.User, nil | |||||
| } | |||||
| org, err := models.GetUserByID(uid) | |||||
| if models.IsErrUserNotExist(err) { | |||||
| return ctx.User, nil | |||||
| } | |||||
| if err != nil { | |||||
| return nil, response.SYSTEM_ERROR | |||||
| } | |||||
| // Check ownership of organization. | |||||
| if !org.IsOrganization() { | |||||
| return nil, nil | |||||
| } | |||||
| if !ctx.User.IsAdmin { | |||||
| canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) | |||||
| if err != nil { | |||||
| return nil, response.NewBizError(err) | |||||
| } else if !canCreate { | |||||
| return nil, response.INSUFFICIENT_PERMISSION | |||||
| } | |||||
| } | |||||
| return org, nil | |||||
| } | |||||
| func handleMigrateError4Api(ctx *context.APIContext, repoOwner *models.User, remoteAddr string, err error) { | |||||
| switch { | |||||
| case models.IsErrRepoAlreadyExist(err): | |||||
| ctx.JSON(http.StatusOK, response.Error(3, "The repository with the same name already exists.")) | |||||
| case migrations.IsRateLimitError(err): | |||||
| ctx.JSON(http.StatusOK, response.ServerError("Remote visit addressed rate limitation.")) | |||||
| case migrations.IsTwoFactorAuthError(err): | |||||
| ctx.JSON(http.StatusOK, response.ServerError("Remote visit required two factors authentication.")) | |||||
| case models.IsErrReachLimitOfRepo(err): | |||||
| ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))) | |||||
| case models.IsErrNameReserved(err): | |||||
| ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))) | |||||
| case models.IsErrNameCharsNotAllowed(err): | |||||
| ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))) | |||||
| case models.IsErrNamePatternNotAllowed(err): | |||||
| ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))) | |||||
| default: | |||||
| err = util.URLSanitizedError(err, remoteAddr) | |||||
| if strings.Contains(err.Error(), "Authentication failed") || | |||||
| strings.Contains(err.Error(), "Bad credentials") || | |||||
| strings.Contains(err.Error(), "could not read Username") { | |||||
| ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Authentication failed: %v.", err))) | |||||
| } else if strings.Contains(err.Error(), "fatal:") { | |||||
| ctx.JSON(http.StatusOK, response.ServerError(fmt.Sprintf("Migration failed: %v.", err))) | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||||
| } | |||||
| } | |||||
| } | |||||
| func QueryRepoSatus(ctx *context.APIContext, form auth.MigrateRepoForm) { | |||||
| ctx.JSON(http.StatusOK, response.Success()) | |||||
| } | } | ||||
| @@ -0,0 +1,155 @@ | |||||
| package tech | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/auth" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| api "code.gitea.io/gitea/modules/structs" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/repository" | |||||
| techService "code.gitea.io/gitea/services/tech" | |||||
| "errors" | |||||
| "fmt" | |||||
| "net/http" | |||||
| ) | |||||
| //CommitOpenIRepo 新建启智项目申请页面提交 | |||||
| func CommitOpenIRepo(ctx *context.APIContext, form api.OpenITechRepo) { | |||||
| //解析项目路径,查找项目 | |||||
| repo, err := repository.FindRepoByUrl(form.Url) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| //查找项目编号 | |||||
| tech, err := models.GetTechByTechNo(form.TechNo) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| //判断承接单位是否在科技项目的参与单位中 | |||||
| if !tech.IsValidInstitution(form.Institution) { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr("tech.institution_not_valid"))) | |||||
| return | |||||
| } | |||||
| //判断是否已经存在了该项目 | |||||
| exist, err := techService.IsValidRepoConvergeExists(repo.ID, tech.ID) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| if exist { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(fmt.Sprintf(ctx.Tr("tech.repo_converge_exists"), tech.ProjectName, repo.Alias))) | |||||
| return | |||||
| } | |||||
| //写入数据库 | |||||
| rci := &models.RepoConvergeInfo{ | |||||
| RepoID: repo.ID, | |||||
| Url: form.Url, | |||||
| BaseInfoID: tech.ID, | |||||
| Institution: form.Institution, | |||||
| UID: ctx.User.ID, | |||||
| Status: models.DefaultTechApprovedStatus, | |||||
| } | |||||
| err = rci.InsertOrUpdate() | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.OuterSuccess()) | |||||
| } | |||||
| //CommitNotOpenIRepo 新建非启智项目申请页面提交 | |||||
| func CommitNotOpenIRepo(ctx *context.APIContext, form api.NotOpenITechRepo) { | |||||
| //触发更新迁移状态 | |||||
| go techService.UpdateTechMigrateStatus() | |||||
| //查找项目编号 | |||||
| tech, err := models.GetTechByTechNo(form.TechNo) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| //判断承接单位是否在科技项目的参与单位中 | |||||
| if !tech.IsValidInstitution(form.Institution) { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr("tech.institution_not_valid"))) | |||||
| return | |||||
| } | |||||
| //调用迁移接口 | |||||
| bizErr := repository.MigrateSubmit(ctx.User, auth.MigrateRepoForm{ | |||||
| CloneAddr: form.Url, | |||||
| UID: form.UID, | |||||
| RepoName: form.RepoName, | |||||
| Alias: form.Alias, | |||||
| Description: form.Description, | |||||
| Labels: false, | |||||
| Mirror: true, | |||||
| }) | |||||
| if bizErr != nil { | |||||
| if bizErr.Code == 3 { | |||||
| ctx.JSON(http.StatusOK, response.OuterError(bizErr.Code, ctx.Tr("tech.to_migrate_repo_exists"))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.OuterError(bizErr.Code, ctx.Tr(bizErr.Err))) | |||||
| return | |||||
| } | |||||
| repo, err := models.GetRepositoryByName(form.UID, form.RepoName) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| //判断是否已经存在了该项目 | |||||
| exist, err := techService.IsValidRepoConvergeExists(repo.ID, tech.ID) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| if exist { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(fmt.Sprintf(ctx.Tr("tech.repo_converge_exists"), tech.ProjectName, repo.Alias))) | |||||
| return | |||||
| } | |||||
| //给仓库加标签 | |||||
| updateTopics(repo.ID, form.Topics) | |||||
| //写入数据库 | |||||
| rci := &models.RepoConvergeInfo{ | |||||
| RepoID: repo.ID, | |||||
| Url: form.Url, | |||||
| BaseInfoID: tech.ID, | |||||
| Institution: form.Institution, | |||||
| UID: ctx.User.ID, | |||||
| Status: models.DefaultTechApprovedStatus, | |||||
| } | |||||
| err = rci.InsertOrUpdate() | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterServerError(ctx.Tr(err.Error()))) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.OuterSuccess()) | |||||
| } | |||||
| func updateTopics(repoId int64, topicNames []string) error { | |||||
| validTopics, invalidTopics := models.SanitizeAndValidateTopics(topicNames) | |||||
| if len(validTopics) > 25 { | |||||
| return errors.New("Exceeding maximum number of topics per repo") | |||||
| } | |||||
| if len(invalidTopics) > 0 { | |||||
| return errors.New("Topic names are invalid") | |||||
| } | |||||
| err := models.SaveTopics(repoId, validTopics...) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -0,0 +1,336 @@ | |||||
| package tech | |||||
| import ( | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "code.gitea.io/gitea/services/role" | |||||
| techService "code.gitea.io/gitea/services/tech" | |||||
| "net/http" | |||||
| "strconv" | |||||
| "strings" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| "github.com/360EntSecGroup-Skylar/excelize/v2" | |||||
| ) | |||||
| func GetFilterInfo(ctx *context.APIContext) { | |||||
| filterType := ctx.QueryInt("type") | |||||
| limit := ctx.QueryInt("limit") | |||||
| if filterType == 0 { | |||||
| projectTypes := models.GetProjectTypes() | |||||
| allInstitutions := models.GetAllInstitutions() | |||||
| applyYears, executeYears := models.GetApplyExecuteYears() | |||||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||||
| "type_name": projectTypes, | |||||
| "institution_name": allInstitutions, | |||||
| "execute_year": executeYears, | |||||
| "apply_year": applyYears, | |||||
| }) | |||||
| return | |||||
| } | |||||
| if filterType == 1 { | |||||
| projectNames := models.GetProjectNames() | |||||
| topics := models.GetTechRepoTopics(limit) | |||||
| allInstitutions := models.GetAllInstitutions() | |||||
| ctx.JSON(http.StatusOK, map[string]interface{}{ | |||||
| "topic": topics, | |||||
| "institution_name": allInstitutions, | |||||
| "project_name": projectNames, | |||||
| }) | |||||
| return | |||||
| } | |||||
| ctx.Error(http.StatusBadRequest, "parameter error", "") | |||||
| } | |||||
| func GetAdminRepoInfo(ctx *context.APIContext) { | |||||
| go techService.UpdateTechStatus() | |||||
| opts := &models.SearchUserRepoOpt{} | |||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| opts.Page = page | |||||
| pageSize := ctx.QueryInt("pageSize") | |||||
| if pageSize <= 0 { | |||||
| pageSize = 15 | |||||
| } | |||||
| opts.PageSize = pageSize | |||||
| infos, total, err := techService.GetAdminRepoInfo(opts) | |||||
| if err != nil { | |||||
| log.Error("search has error", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": 0, "data": []*models.TechRepoInfoAdmin{}}) | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": total, "data": infos}) | |||||
| } | |||||
| } | |||||
| func Action(ctx *context.APIContext, ids ActionIDs) { | |||||
| if len(ids.ID) == 0 { | |||||
| ctx.JSON(http.StatusOK, models.BaseOKMessageApi) | |||||
| } | |||||
| var err error | |||||
| switch ctx.Params(":action") { | |||||
| case "show": | |||||
| err = models.ShowTechRepo(ids.ID, true) | |||||
| case "hide": | |||||
| err = models.ShowTechRepo(ids.ID, false) | |||||
| } | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("tech.show_or_hide_fail", ctx.Params(":action")))) | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, models.BaseOKMessage) | |||||
| } | |||||
| } | |||||
| type ActionIDs struct { | |||||
| ID []int64 `json:"id"` | |||||
| } | |||||
| func GetMyRepoInfo(ctx *context.APIContext) { | |||||
| go techService.UpdateTechStatus() | |||||
| opts := &models.SearchUserRepoOpt{} | |||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| opts.Page = page | |||||
| pageSize := ctx.QueryInt("pageSize") | |||||
| if pageSize <= 0 { | |||||
| pageSize = 15 | |||||
| } | |||||
| opts.PageSize = pageSize | |||||
| opts.User = ctx.User | |||||
| infos, total, err := techService.GetMyRepoInfo(opts) | |||||
| if err != nil { | |||||
| log.Error("search has error", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": 0, "data": []*models.TechRepoInfoUser{}}) | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": total, "data": infos}) | |||||
| } | |||||
| } | |||||
| func SearchRepoInfo(ctx *context.APIContext) { | |||||
| go techService.UpdateTechStatus() | |||||
| opts := &models.SearchRepoOpt{} | |||||
| opts.Q = ctx.Query("name") | |||||
| opts.ProjectName = ctx.Query("tech_name") | |||||
| opts.Topic = ctx.Query("topic") | |||||
| opts.Institution = ctx.Query("institution_name") | |||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| opts.Page = page | |||||
| pageSize := ctx.QueryInt("pageSize") | |||||
| if pageSize <= 0 { | |||||
| pageSize = 30 | |||||
| } | |||||
| opts.PageSize = pageSize | |||||
| orderBy := ctx.Query("sort") | |||||
| opts.OrderBy = orderBy | |||||
| infos, total, err := techService.SearchRepoInfoWithInstitution(opts) | |||||
| if err != nil { | |||||
| log.Error("search has error", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": 0, "data": []*models.RepoWithInstitution{}}) | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": total, "data": infos}) | |||||
| } | |||||
| } | |||||
| func SearchTechProjectInfo(ctx *context.APIContext) { | |||||
| go techService.UpdateTechStatus() | |||||
| opts := &models.SearchTechOpt{} | |||||
| opts.Q = ctx.Query("name") | |||||
| opts.ApplyYear = ctx.QueryInt("apply_year") | |||||
| opts.ExecuteYear = ctx.QueryInt("execute_year") | |||||
| opts.Institution = ctx.Query("institution_name") | |||||
| opts.ProjectType = ctx.Query("type_name") | |||||
| page := ctx.QueryInt("page") | |||||
| if page <= 0 { | |||||
| page = 1 | |||||
| } | |||||
| opts.Page = page | |||||
| pageSize := ctx.QueryInt("pageSize") | |||||
| if pageSize <= 0 { | |||||
| pageSize = 15 | |||||
| } | |||||
| opts.PageSize = pageSize | |||||
| orderBy := ctx.Query("sort") | |||||
| if orderBy == "" || orderBy == "count" { | |||||
| opts.OrderBy = "order by repo_count desc, max desc " | |||||
| } else { | |||||
| opts.OrderBy = "order by max desc, repo_count desc " | |||||
| } | |||||
| infos, total, err := models.SearchTechRepoInfo(opts) | |||||
| if err != nil { | |||||
| log.Error("search has error", err) | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": 0, "data": []*models.TechRepoInfo{}}) | |||||
| } else { | |||||
| ctx.JSON(http.StatusOK, map[string]interface { | |||||
| }{"total": total, "data": infos}) | |||||
| } | |||||
| } | |||||
| func ImportBasicInfo(ctx *context.APIContext) { | |||||
| file, _, err := ctx.GetFile("file") | |||||
| if err != nil { | |||||
| log.Error("get file err", err) | |||||
| ctx.JSON(http.StatusBadRequest, models.BaseErrorMessageApi(ctx.Tr("tech.get_file_fail"))) | |||||
| return | |||||
| } | |||||
| defer file.Close() | |||||
| f, err := excelize.OpenReader(file) | |||||
| if err != nil { | |||||
| log.Error("open file err", err) | |||||
| ctx.JSON(http.StatusBadRequest, models.BaseErrorMessageApi(ctx.Tr("tech.content_type_unsupported"))) | |||||
| return | |||||
| } | |||||
| rows, err := f.GetRows(f.GetSheetName(1)) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusBadRequest, models.BaseErrorMessageApi(ctx.Tr("tech.content_type_unsupported"))) | |||||
| return | |||||
| } | |||||
| for i, row := range rows { | |||||
| if i != 0 { | |||||
| baseInfo := &models.TechConvergeBaseInfo{} | |||||
| baseInfo.ProjectNumber = strings.TrimSpace(row[2]) | |||||
| tbInDB, err := models.GetTechConvergeBaseInfoByProjectNumber(baseInfo.ProjectNumber) | |||||
| if err != nil && !models.IsErrTechConvergeBaseInfoNotExist(err) { | |||||
| log.Error("query err ", i, err) | |||||
| ctx.JSON(http.StatusBadRequest, models.BaseErrorMessageApi(ctx.Tr("tech.sql_err"))) | |||||
| return | |||||
| } | |||||
| if tbInDB != nil { | |||||
| baseInfo = tbInDB | |||||
| } | |||||
| baseInfo.ApplyYear, err = strconv.Atoi(strings.TrimSpace(row[1])) | |||||
| if err != nil { | |||||
| log.Warn("base info apply year parse err ", i) | |||||
| } | |||||
| baseInfo.ProjectName = strings.TrimSpace(row[3]) | |||||
| baseInfo.Type = strings.TrimSpace(row[16]) | |||||
| baseInfo.Owner = strings.TrimSpace(row[8]) | |||||
| baseInfo.Email = strings.TrimSpace(row[10]) | |||||
| baseInfo.Phone = strings.TrimSpace(row[9]) | |||||
| baseInfo.Recommend = strings.TrimSpace(row[7]) | |||||
| baseInfo.Institution = strings.TrimSpace(row[4]) | |||||
| baseInfo.Category = strings.TrimSpace(row[6]) | |||||
| baseInfo.Province = strings.TrimSpace(row[5]) | |||||
| baseInfo.Contact = strings.TrimSpace(row[11]) | |||||
| baseInfo.ContactPhone = strings.TrimSpace(row[12]) | |||||
| baseInfo.ContactEmail = strings.TrimSpace(row[13]) | |||||
| baseInfo.ExecuteMonth, _ = strconv.Atoi(strings.TrimSpace(row[14])) | |||||
| baseInfo.ExecutePeriod = strings.TrimSpace(row[15]) | |||||
| baseInfo.ExecuteStartYear, baseInfo.ExecuteEndYear = getBeginEndYear(baseInfo.ExecutePeriod) | |||||
| baseInfo.StartUp = strings.TrimSpace(row[17]) | |||||
| baseInfo.NumberTopic, _ = strconv.Atoi(strings.TrimSpace(row[30])) | |||||
| baseInfo.Topic1 = strings.TrimSpace(row[31]) | |||||
| baseInfo.Topic2 = strings.TrimSpace(row[32]) | |||||
| baseInfo.Topic3 = strings.TrimSpace(row[33]) | |||||
| baseInfo.Topic4 = strings.TrimSpace(row[34]) | |||||
| baseInfo.Topic5 = strings.TrimSpace(row[35]) | |||||
| allIns := replaceNewLineWithComma(strings.TrimSpace(row[36])) | |||||
| if allIns != "" { | |||||
| allInsArray := strings.Split(allIns, ",") | |||||
| var allInsValid []string | |||||
| for _, ins := range allInsArray { | |||||
| if ins != "" { | |||||
| allInsValid = append(allInsValid, ins) | |||||
| } | |||||
| } | |||||
| baseInfo.AllInstitution = strings.Join(allInsValid, ",") | |||||
| } | |||||
| if baseInfo.AllInstitution == "" { | |||||
| baseInfo.AllInstitution = baseInfo.Institution | |||||
| } | |||||
| err = baseInfo.InsertOrUpdate() | |||||
| if err != nil { | |||||
| log.Error("update err", i, err) | |||||
| ctx.JSON(http.StatusBadRequest, models.BaseErrorMessageApi(ctx.Tr("tech.sql_err"))) | |||||
| return | |||||
| } | |||||
| } | |||||
| } | |||||
| ctx.JSON(http.StatusOK, models.BaseOKMessageApi) | |||||
| } | |||||
| func replaceNewLineWithComma(s string) string { | |||||
| const replacement = "," | |||||
| var replacer = strings.NewReplacer( | |||||
| "\r\n", replacement, | |||||
| "\r", replacement, | |||||
| "\n", replacement, | |||||
| "\v", replacement, | |||||
| "\f", replacement, | |||||
| "\u0085", replacement, | |||||
| "\u2028", replacement, | |||||
| "\u2029", replacement, | |||||
| ) | |||||
| return replacer.Replace(s) | |||||
| } | |||||
| /** | |||||
| input:2019.12-2023.12 output: 2019,2023 | |||||
| */ | |||||
| func getBeginEndYear(executePeriod string) (int, int) { | |||||
| period := strings.Split(executePeriod, "-") | |||||
| if len(period) == 2 { | |||||
| start := strings.Split(period[0], ".") | |||||
| end := strings.Split(period[1], ".") | |||||
| if len(start) == 2 && len(end) == 2 { | |||||
| startYear, err := strconv.Atoi(start[0]) | |||||
| endYear, err1 := strconv.Atoi(end[0]) | |||||
| if err == nil && err1 == nil { | |||||
| return startYear, endYear | |||||
| } | |||||
| } | |||||
| } | |||||
| return 0, 0 | |||||
| } | |||||
| func FindTech(ctx *context.APIContext) { | |||||
| techNo := ctx.Query("no") | |||||
| name := ctx.Query("name") | |||||
| institution := ctx.Query("institution") | |||||
| r, err := techService.FindTech(models.FindTechOpt{TechNo: techNo, ProjectName: name, Institution: institution}) | |||||
| if err != nil { | |||||
| ctx.JSON(http.StatusOK, response.OuterResponseError(err)) | |||||
| return | |||||
| } | |||||
| ctx.JSON(http.StatusOK, response.OuterSuccessWithData(r)) | |||||
| } | |||||
| func IsAdmin(ctx *context.APIContext) { | |||||
| isAdmin := role.UserHasRole(ctx.User.ID, models.TechProgramAdmin) | |||||
| r := map[string]interface{}{} | |||||
| r["is_admin"] = isAdmin | |||||
| ctx.JSON(http.StatusOK, response.OuterSuccessWithData(r)) | |||||
| } | |||||
| @@ -28,3 +28,6 @@ func OuterSuccessWithData(data interface{}) *AiforgeOuterResponse { | |||||
| func OuterErrorWithData(code int, msg string, data interface{}) *AiforgeOuterResponse { | func OuterErrorWithData(code int, msg string, data interface{}) *AiforgeOuterResponse { | ||||
| return &AiforgeOuterResponse{Code: code, Msg: msg, Data: data} | return &AiforgeOuterResponse{Code: code, Msg: msg, Data: data} | ||||
| } | } | ||||
| func OuterResponseError(err error) *AiforgeOuterResponse { | |||||
| return &AiforgeOuterResponse{Code: RESPONSE_CODE_ERROR_DEFAULT, Msg: err.Error()} | |||||
| } | |||||
| @@ -12,3 +12,7 @@ func (b BizError) Error() string { | |||||
| func NewBizError(err error) *BizError { | func NewBizError(err error) *BizError { | ||||
| return &BizError{Code: RESPONSE_CODE_ERROR_DEFAULT, Err: err.Error()} | return &BizError{Code: RESPONSE_CODE_ERROR_DEFAULT, Err: err.Error()} | ||||
| } | } | ||||
| func BuildBizError(code int, msg string) *BizError { | |||||
| return &BizError{Code: code, Err: msg} | |||||
| } | |||||
| @@ -12,6 +12,8 @@ import ( | |||||
| "text/template" | "text/template" | ||||
| "time" | "time" | ||||
| "code.gitea.io/gitea/routers/tech" | |||||
| "code.gitea.io/gitea/routers/badge" | "code.gitea.io/gitea/routers/badge" | ||||
| "code.gitea.io/gitea/routers/reward/point" | "code.gitea.io/gitea/routers/reward/point" | ||||
| "code.gitea.io/gitea/routers/task" | "code.gitea.io/gitea/routers/task" | ||||
| @@ -896,6 +898,15 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Get("/check_name", repo.CheckName) | m.Get("/check_name", repo.CheckName) | ||||
| }, reqSignIn) | }, reqSignIn) | ||||
| m.Group("/tech", func() { | |||||
| m.Get("/new", tech.Create) | |||||
| m.Get("/tech_view", tech.TechView) | |||||
| m.Get("/repo_view", tech.RepoView) | |||||
| m.Get("/admin_view", tech.AdminView) | |||||
| m.Get("/my_view", tech.MyView) | |||||
| }, reqSignIn) | |||||
| // ***** Release Attachment Download without Signin | // ***** Release Attachment Download without Signin | ||||
| m.Get("/:username/:reponame/releases/download/:vTag/:fileName", ignSignIn, context.RepoAssignment(), repo.MustBeNotEmpty, repo.RedirectDownload) | m.Get("/:username/:reponame/releases/download/:vTag/:fileName", ignSignIn, context.RepoAssignment(), repo.MustBeNotEmpty, repo.RedirectDownload) | ||||
| @@ -0,0 +1,32 @@ | |||||
| package tech | |||||
| import ( | |||||
| "code.gitea.io/gitea/modules/base" | |||||
| "code.gitea.io/gitea/modules/context" | |||||
| ) | |||||
| const ( | |||||
| tplCreate base.TplName = "tech/create" | |||||
| tplTech base.TplName = "tech/tech_view" | |||||
| tplRepo base.TplName = "tech/repo_view" | |||||
| tplAdmin base.TplName = "tech/admin_view" | |||||
| tplMine base.TplName = "tech/my_view" | |||||
| ) | |||||
| func Create(ctx *context.Context) { | |||||
| ctx.HTML(200, tplCreate) | |||||
| } | |||||
| func TechView(ctx *context.Context) { | |||||
| ctx.HTML(200, tplTech) | |||||
| } | |||||
| func RepoView(ctx *context.Context) { | |||||
| ctx.HTML(200, tplRepo) | |||||
| } | |||||
| func AdminView(ctx *context.Context) { | |||||
| ctx.HTML(200, tplAdmin) | |||||
| } | |||||
| func MyView(ctx *context.Context) { | |||||
| ctx.HTML(200, tplMine) | |||||
| } | |||||
| @@ -0,0 +1,146 @@ | |||||
| package repository | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/auth" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/migrations" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| api "code.gitea.io/gitea/modules/structs" | |||||
| "code.gitea.io/gitea/modules/task" | |||||
| "code.gitea.io/gitea/modules/util" | |||||
| "code.gitea.io/gitea/routers/response" | |||||
| "errors" | |||||
| "fmt" | |||||
| "net/url" | |||||
| "strings" | |||||
| ) | |||||
| func MigrateSubmit(currentUser *models.User, form auth.MigrateRepoForm) *response.BizError { | |||||
| log.Info("receive MigrateSubmit request") | |||||
| ctxUser, bizErr := checkMigrateUser(currentUser, form.UID) | |||||
| if bizErr != nil { | |||||
| return bizErr | |||||
| } | |||||
| remoteAddr, err := form.ParseRemoteAddr(currentUser) | |||||
| if err != nil { | |||||
| if models.IsErrInvalidCloneAddr(err) { | |||||
| addrErr := err.(models.ErrInvalidCloneAddr) | |||||
| switch { | |||||
| case addrErr.IsURLError: | |||||
| return response.PARAM_ERROR | |||||
| case addrErr.IsPermissionDenied: | |||||
| return response.INSUFFICIENT_PERMISSION | |||||
| case addrErr.IsInvalidPath: | |||||
| return response.PARAM_ERROR | |||||
| default: | |||||
| } | |||||
| } | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| var gitServiceType = api.PlainGitService | |||||
| u, err := url.Parse(form.CloneAddr) | |||||
| if err == nil && strings.EqualFold(u.Host, "github.com") { | |||||
| gitServiceType = api.GithubService | |||||
| } | |||||
| var opts = migrations.MigrateOptions{ | |||||
| OriginalURL: form.CloneAddr, | |||||
| GitServiceType: gitServiceType, | |||||
| CloneAddr: remoteAddr, | |||||
| RepoName: form.RepoName, | |||||
| Alias: form.Alias, | |||||
| Description: form.Description, | |||||
| Private: form.Private || setting.Repository.ForcePrivate, | |||||
| Mirror: form.Mirror, | |||||
| AuthUsername: form.AuthUsername, | |||||
| AuthPassword: form.AuthPassword, | |||||
| Wiki: form.Wiki, | |||||
| Issues: form.Issues, | |||||
| Milestones: form.Milestones, | |||||
| Labels: form.Labels, | |||||
| Comments: true, | |||||
| PullRequests: form.PullRequests, | |||||
| Releases: form.Releases, | |||||
| } | |||||
| if opts.Mirror { | |||||
| opts.Issues = false | |||||
| opts.Milestones = false | |||||
| opts.Labels = false | |||||
| opts.Comments = false | |||||
| opts.PullRequests = false | |||||
| opts.Releases = false | |||||
| } | |||||
| err = models.CheckCreateRepository(currentUser, ctxUser, opts.RepoName, opts.Alias) | |||||
| if err != nil { | |||||
| return handleMigrateError4Api(ctxUser, remoteAddr, err) | |||||
| } | |||||
| err = task.MigrateRepository(currentUser, ctxUser, opts) | |||||
| if err != nil { | |||||
| return handleMigrateError4Api(ctxUser, remoteAddr, err) | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func checkMigrateUser(currentUser *models.User, uid int64) (*models.User, *response.BizError) { | |||||
| if uid == currentUser.ID || uid == 0 { | |||||
| return currentUser, nil | |||||
| } | |||||
| org, err := models.GetUserByID(uid) | |||||
| if models.IsErrUserNotExist(err) { | |||||
| return currentUser, nil | |||||
| } | |||||
| if err != nil { | |||||
| return nil, response.SYSTEM_ERROR | |||||
| } | |||||
| // Check ownership of organization. | |||||
| if !org.IsOrganization() { | |||||
| return nil, nil | |||||
| } | |||||
| if !currentUser.IsAdmin { | |||||
| canCreate, err := org.CanCreateOrgRepo(currentUser.ID) | |||||
| if err != nil { | |||||
| return nil, response.NewBizError(err) | |||||
| } else if !canCreate { | |||||
| return nil, response.INSUFFICIENT_PERMISSION | |||||
| } | |||||
| } | |||||
| return org, nil | |||||
| } | |||||
| func handleMigrateError4Api(repoOwner *models.User, remoteAddr string, err error) *response.BizError { | |||||
| switch { | |||||
| case models.IsErrRepoAlreadyExist(err): | |||||
| return response.BuildBizError(3, "The repository with the same name already exists.") | |||||
| case migrations.IsRateLimitError(err): | |||||
| return response.NewBizError(errors.New("Remote visit addressed rate limitation.")) | |||||
| case migrations.IsTwoFactorAuthError(err): | |||||
| return response.NewBizError(errors.New("Remote visit required two factors authentication.")) | |||||
| case models.IsErrReachLimitOfRepo(err): | |||||
| return response.NewBizError(errors.New(fmt.Sprintf("You have already reached your limit of %d repositories.", repoOwner.MaxCreationLimit()))) | |||||
| case models.IsErrNameReserved(err): | |||||
| return response.NewBizError(errors.New(fmt.Sprintf("The username '%s' is reserved.", err.(models.ErrNameReserved).Name))) | |||||
| case models.IsErrNameCharsNotAllowed(err): | |||||
| return response.NewBizError(errors.New(fmt.Sprintf("The username '%s' contains invalid characters.", err.(models.ErrNameCharsNotAllowed).Name))) | |||||
| case models.IsErrNamePatternNotAllowed(err): | |||||
| return response.NewBizError(errors.New(fmt.Sprintf("The pattern '%s' is not allowed in a username.", err.(models.ErrNamePatternNotAllowed).Pattern))) | |||||
| default: | |||||
| err = util.URLSanitizedError(err, remoteAddr) | |||||
| if strings.Contains(err.Error(), "Authentication failed") || | |||||
| strings.Contains(err.Error(), "Bad credentials") || | |||||
| strings.Contains(err.Error(), "could not read Username") { | |||||
| return response.NewBizError(errors.New((fmt.Sprintf("Authentication failed: %v.", err)))) | |||||
| } else if strings.Contains(err.Error(), "fatal:") { | |||||
| return response.NewBizError(errors.New((fmt.Sprintf("Migration failed: %v.", err)))) | |||||
| } | |||||
| } | |||||
| return response.NewBizError(err) | |||||
| } | |||||
| @@ -169,6 +169,7 @@ type FindReposOptions struct { | |||||
| Topic string | Topic string | ||||
| Private bool | Private bool | ||||
| OwnerID int64 | OwnerID int64 | ||||
| RepoIds []int64 | |||||
| } | } | ||||
| func FindRepos(opts FindReposOptions) (*models.FindReposResponse, error) { | func FindRepos(opts FindReposOptions) (*models.FindReposResponse, error) { | ||||
| @@ -224,6 +225,7 @@ func FindRepos(opts FindReposOptions) (*models.FindReposResponse, error) { | |||||
| AllLimited: true, | AllLimited: true, | ||||
| TopicName: opts.Topic, | TopicName: opts.Topic, | ||||
| IncludeDescription: setting.UI.SearchRepoDescription, | IncludeDescription: setting.UI.SearchRepoDescription, | ||||
| RepoIds: opts.RepoIds, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| log.Error("FindRepos error when SearchRepository.%v", err) | log.Error("FindRepos error when SearchRepository.%v", err) | ||||
| @@ -0,0 +1,49 @@ | |||||
| package repository | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| "errors" | |||||
| "net/url" | |||||
| "strings" | |||||
| ) | |||||
| func FindRepoByUrl(url string) (*models.Repository, error) { | |||||
| ownerName, repoName := parseOpenIUrl(url) | |||||
| if ownerName == "" || repoName == "" { | |||||
| return nil, errors.New("tech.incorrect_openi_format") | |||||
| } | |||||
| r, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | |||||
| if err != nil { | |||||
| if models.IsErrRepoNotExist(err) { | |||||
| return nil, errors.New("tech.openi_repo_not_exist") | |||||
| } | |||||
| return nil, err | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| //parseOpenIUrl parse openI repo url,return ownerName and repoName | |||||
| func parseOpenIUrl(u string) (string, string) { | |||||
| url, err := url.Parse(u) | |||||
| if err != nil { | |||||
| return "", "" | |||||
| } | |||||
| appUrl, err := url.Parse(setting.AppURL) | |||||
| if err != nil { | |||||
| return "", "" | |||||
| } | |||||
| if appUrl.Host != url.Host { | |||||
| return "", "" | |||||
| } | |||||
| array := strings.Split(url.Path, "/") | |||||
| if len(array) < 3 { | |||||
| return "", "" | |||||
| } | |||||
| ownerName := array[1] | |||||
| repoName := array[2] | |||||
| return ownerName, repoName | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| package role | |||||
| import "code.gitea.io/gitea/models" | |||||
| var roleMap = map[models.RoleType]*models.Role{ | |||||
| models.TechProgramAdmin: { | |||||
| Type: models.TechProgramAdmin, | |||||
| Name: "科技项目管理员", | |||||
| Description: "拥有科技项目管理相关功能的管理员权限", | |||||
| }, | |||||
| } | |||||
| func GetRole(roleType models.RoleType) *models.Role { | |||||
| return roleMap[roleType] | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| package role | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| pg "github.com/lib/pq" | |||||
| ) | |||||
| func AddUserRole(userId int64, roleType models.RoleType) error { | |||||
| role := GetRole(roleType) | |||||
| if role == nil { | |||||
| return models.ErrRoleNotExists{} | |||||
| } | |||||
| _, err := models.NewUserRole(models.UserRole{UserId: userId, RoleType: roleType}) | |||||
| if err != nil { | |||||
| e := err.(*pg.Error) | |||||
| //23505 is postgrey error code for unique_violation | |||||
| //see https://www.postgresql.org/docs/current/errcodes-appendix.html | |||||
| //if unique_violation,user role record exists,just return | |||||
| if e.Code == "23505" { | |||||
| return nil | |||||
| } | |||||
| } | |||||
| return err | |||||
| } | |||||
| func DeleteUserRole(userId int64, roleType models.RoleType) error { | |||||
| role := GetRole(roleType) | |||||
| if role == nil { | |||||
| return models.ErrRoleNotExists{} | |||||
| } | |||||
| _, err := models.DeleteUserRole(roleType, userId) | |||||
| return err | |||||
| } | |||||
| func UserHasRole(userId int64, roleType models.RoleType) bool { | |||||
| role := GetRole(roleType) | |||||
| if role == nil { | |||||
| return false | |||||
| } | |||||
| _, err := models.GetUserRoleByUserAndRole(userId, roleType) | |||||
| if err != nil { | |||||
| return false | |||||
| } | |||||
| return true | |||||
| } | |||||
| @@ -0,0 +1,234 @@ | |||||
| package tech | |||||
| import ( | |||||
| "code.gitea.io/gitea/models" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/services/repository" | |||||
| ) | |||||
| func FindTech(opt models.FindTechOpt) ([]*models.TechConvergeBrief, error) { | |||||
| techList, err := models.FindTech(opt) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| r := make([]*models.TechConvergeBrief, len(techList)) | |||||
| for i := 0; i < len(techList); i++ { | |||||
| r[i] = techList[i].Brief() | |||||
| } | |||||
| return r, nil | |||||
| } | |||||
| func GetMyRepoInfo(opts *models.SearchUserRepoOpt) ([]*models.TechRepoInfoUser, int64, error) { | |||||
| infos, total, err := models.GetTechRepoInfoForUser(opts) | |||||
| if err != nil { | |||||
| return nil, total, err | |||||
| } | |||||
| result := make([]*models.TechRepoInfoUser, 0, total) | |||||
| for _, info := range infos { | |||||
| techRepoInfoUser := &models.TechRepoInfoUser{ | |||||
| ID: info.ID, | |||||
| Url: info.Url, | |||||
| Status: info.Status, | |||||
| ContributionInstitution: info.Institution, | |||||
| CreatedUnix: info.CreatedUnix, | |||||
| UpdatedUnix: info.UpdatedUnix, | |||||
| } | |||||
| if info.User != nil { | |||||
| techRepoInfoUser.UserName = info.User.Name | |||||
| } | |||||
| if info.BaseInfo != nil { | |||||
| baseInfo := info.BaseInfo | |||||
| techRepoInfoUser.ProjectNumber = baseInfo.ProjectNumber | |||||
| techRepoInfoUser.ProjectName = baseInfo.ProjectName | |||||
| techRepoInfoUser.Institution = baseInfo.Institution | |||||
| techRepoInfoUser.AllInstitution = baseInfo.AllInstitution | |||||
| } | |||||
| if info.Repo != nil { | |||||
| techRepoInfoUser.RepoName = info.Repo.Name | |||||
| techRepoInfoUser.RepoOwnerName = info.Repo.OwnerName | |||||
| } | |||||
| result = append(result, techRepoInfoUser) | |||||
| } | |||||
| return result, total, nil | |||||
| } | |||||
| func GetAdminRepoInfo(opts *models.SearchUserRepoOpt) ([]*models.TechRepoInfoAdmin, int64, error) { | |||||
| infos, total, err := models.GetTechRepoInfoForUser(opts) | |||||
| if err != nil { | |||||
| return nil, total, err | |||||
| } | |||||
| result := make([]*models.TechRepoInfoAdmin, 0, total) | |||||
| for _, info := range infos { | |||||
| techRepoInfoAdmin := &models.TechRepoInfoAdmin{ | |||||
| ID: info.ID, | |||||
| Url: info.Url, | |||||
| Status: info.Status, | |||||
| ContributionInstitution: info.Institution, | |||||
| CreatedUnix: info.CreatedUnix, | |||||
| UpdatedUnix: info.UpdatedUnix, | |||||
| } | |||||
| if info.User != nil { | |||||
| techRepoInfoAdmin.UserName = info.User.Name | |||||
| } | |||||
| if info.BaseInfo != nil { | |||||
| baseInfo := info.BaseInfo | |||||
| techRepoInfoAdmin.ProjectNumber = baseInfo.ProjectNumber | |||||
| techRepoInfoAdmin.ProjectName = baseInfo.ProjectName | |||||
| techRepoInfoAdmin.Institution = baseInfo.Institution | |||||
| techRepoInfoAdmin.ApplyYear = baseInfo.ApplyYear | |||||
| techRepoInfoAdmin.Province = baseInfo.Province | |||||
| techRepoInfoAdmin.Category = baseInfo.Category | |||||
| techRepoInfoAdmin.Owner = baseInfo.Owner | |||||
| techRepoInfoAdmin.Recommend = baseInfo.Recommend | |||||
| techRepoInfoAdmin.Phone = baseInfo.Phone | |||||
| techRepoInfoAdmin.Email = baseInfo.Email | |||||
| techRepoInfoAdmin.Contact = baseInfo.Contact | |||||
| techRepoInfoAdmin.ContactPhone = baseInfo.ContactPhone | |||||
| techRepoInfoAdmin.ContactEmail = baseInfo.ContactEmail | |||||
| techRepoInfoAdmin.ExecuteMonth = baseInfo.ExecuteMonth | |||||
| techRepoInfoAdmin.ExecutePeriod = baseInfo.ExecutePeriod | |||||
| techRepoInfoAdmin.Type = baseInfo.Type | |||||
| techRepoInfoAdmin.StartUp = baseInfo.StartUp | |||||
| techRepoInfoAdmin.NumberTopic = baseInfo.NumberTopic | |||||
| techRepoInfoAdmin.Topic1 = baseInfo.Topic1 | |||||
| techRepoInfoAdmin.Topic2 = baseInfo.Topic2 | |||||
| techRepoInfoAdmin.Topic3 = baseInfo.Topic3 | |||||
| techRepoInfoAdmin.Topic4 = baseInfo.Topic4 | |||||
| techRepoInfoAdmin.Topic5 = baseInfo.Topic5 | |||||
| techRepoInfoAdmin.AllInstitution = baseInfo.AllInstitution | |||||
| } | |||||
| if info.Repo != nil { | |||||
| techRepoInfoAdmin.RepoName = info.Repo.Name | |||||
| techRepoInfoAdmin.RepoOwnerName = info.Repo.OwnerName | |||||
| } | |||||
| result = append(result, techRepoInfoAdmin) | |||||
| } | |||||
| return result, total, nil | |||||
| } | |||||
| func SearchRepoInfoWithInstitution(opt *models.SearchRepoOpt) ([]*models.RepoWithInstitution, int64, error) { | |||||
| infos, err := models.GetAvailableRepoConvergeInfo(opt) | |||||
| if err != nil { | |||||
| return nil, 0, err | |||||
| } | |||||
| if len(infos) == 0 { | |||||
| return []*models.RepoWithInstitution{}, 0, nil | |||||
| } | |||||
| repoIds, institutionMap := getRepoIdAndInstitutionMap(infos) | |||||
| result, err := repository.FindRepos(repository.FindReposOptions{ | |||||
| ListOptions: models.ListOptions{Page: opt.Page, PageSize: opt.PageSize}, | |||||
| Sort: opt.OrderBy, | |||||
| Keyword: opt.Q, | |||||
| Topic: opt.Topic, | |||||
| RepoIds: repoIds, | |||||
| }) | |||||
| if err != nil { | |||||
| return nil, 0, err | |||||
| } | |||||
| repoResult := make([]*models.RepoWithInstitution, 0, len(result.Repos)) | |||||
| for _, repo := range result.Repos { | |||||
| repoResult = append(repoResult, &models.RepoWithInstitution{ | |||||
| ID: repo.ID, | |||||
| OwnerID: repo.OwnerID, | |||||
| OwnerName: repo.OwnerName, | |||||
| Name: repo.Name, | |||||
| Alias: repo.Alias, | |||||
| Topics: repo.Topics, | |||||
| Description: repo.Description, | |||||
| Institution: institutionMap[repo.ID], | |||||
| RelAvatarLink: repo.RelAvatarLink, | |||||
| UpdatedUnix: repo.UpdatedUnix, | |||||
| }) | |||||
| } | |||||
| return repoResult, result.Total, nil | |||||
| } | |||||
| func getRepoIdAndInstitutionMap(infos []*models.RepoConvergeInfo) ([]int64, map[int64]string) { | |||||
| repoIds := make([]int64, 0, len(infos)) | |||||
| institutionMap := make(map[int64]string, 0) | |||||
| // We only need the keys | |||||
| for _, info := range infos { | |||||
| repoIds = append(repoIds, info.RepoID) | |||||
| institutionMap[info.RepoID] = info.Institution | |||||
| } | |||||
| return repoIds, institutionMap | |||||
| } | |||||
| func IsValidRepoConvergeExists(repoId, baseInfoId int64) (bool, error) { | |||||
| list, err := models.GetRepoConverge(models.GetRepoConvergeOpts{ | |||||
| Status: []int{models.TechHide, models.TechShow, models.TechMigrating}, | |||||
| RepoId: repoId, | |||||
| BaseInfoId: baseInfoId, | |||||
| }) | |||||
| if err != nil { | |||||
| return false, err | |||||
| } | |||||
| return len(list) > 0, nil | |||||
| } | |||||
| func UpdateTechStatus() { | |||||
| err := UpdateTechRepoDeleteStatus() | |||||
| if err != nil { | |||||
| log.Error("update delete status fail", err) | |||||
| } | |||||
| err = UpdateTechMigrateStatus() | |||||
| if err != nil { | |||||
| log.Error("update migrate status fail", err) | |||||
| } | |||||
| } | |||||
| func UpdateTechRepoDeleteStatus() error { | |||||
| needDeleteCheckRepos, err := models.GetRepoConverge(models.GetRepoConvergeOpts{ | |||||
| Status: []int{models.TechShow, models.TechHide}, | |||||
| }) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| for _, r := range needDeleteCheckRepos { | |||||
| _, err := models.GetRepositoryByID(r.RepoID) | |||||
| if err != nil && models.IsErrRepoNotExist(err) { | |||||
| models.UpdateRepoConvergeStatus(r.ID, models.TechNotExist) | |||||
| continue | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func UpdateTechMigrateStatus() error { | |||||
| migratingRepos, err := models.GetRepoConverge(models.GetRepoConvergeOpts{ | |||||
| Status: []int{models.TechMigrating}, | |||||
| }) | |||||
| if err != nil { | |||||
| return err | |||||
| } | |||||
| for _, r := range migratingRepos { | |||||
| repo, err := models.GetRepositoryByID(r.RepoID) | |||||
| if err != nil { | |||||
| if models.IsErrRepoNotExist(err) { | |||||
| models.UpdateRepoConvergeStatus(r.ID, models.TechMigrateFailed) | |||||
| continue | |||||
| } | |||||
| continue | |||||
| } | |||||
| if repo.Status == models.RepositoryReady { | |||||
| models.UpdateRepoConvergeStatus(r.ID, models.DefaultTechApprovedStatus) | |||||
| continue | |||||
| } | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -55,7 +55,8 @@ | |||||
| {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | ||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageHome}} | {{else if .IsLandingPageHome}} | ||||
| @@ -97,6 +98,7 @@ | |||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | ||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageExplore}} | {{else if .IsLandingPageExplore}} | ||||
| @@ -52,7 +52,8 @@ | |||||
| {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | ||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageHome}} | {{else if .IsLandingPageHome}} | ||||
| @@ -92,6 +93,7 @@ | |||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | ||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageExplore}} | {{else if .IsLandingPageExplore}} | ||||
| @@ -44,7 +44,8 @@ | |||||
| {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | ||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageHome}} | {{else if .IsLandingPageHome}} | ||||
| @@ -85,6 +86,7 @@ | |||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | ||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageExplore}} | {{else if .IsLandingPageExplore}} | ||||
| @@ -54,7 +54,8 @@ | |||||
| {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | {{/* <a class="item" href="{{AppSubUrl}}/explore/data_analysis">{{.i18n.Tr "explore.data_analysis"}}</a> */}} | ||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | |||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageHome}} | {{else if .IsLandingPageHome}} | ||||
| @@ -95,6 +96,7 @@ | |||||
| <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | <a class="item" href="{{AppSubUrl}}/kanban/index.html" target="_blank" rel="opener">{{.i18n.Tr "explore.data_analysis"}}</a> | ||||
| {{end}} | {{end}} | ||||
| <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | <a class="item" href="{{AppSubUrl}}/OpenI">{{.i18n.Tr "custom.head.openi.repo"}}</a> | ||||
| <a class="item" href="{{AppSubUrl}}/tech/tech_view">2030科技项目</a> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| {{else if .IsLandingPageExplore}} | {{else if .IsLandingPageExplore}} | ||||
| @@ -0,0 +1,5 @@ | |||||
| {{template "base/head_home" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-tech-adminview.css?v={{MD5 AppVer}}" /> | |||||
| <div id="__vue-root"></div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-tech-adminview.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,5 @@ | |||||
| {{template "base/head_home" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-tech-create.css?v={{MD5 AppVer}}" /> | |||||
| <div id="__vue-root"></div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-tech-create.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,5 @@ | |||||
| {{template "base/head_home" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-tech-myview.css?v={{MD5 AppVer}}" /> | |||||
| <div id="__vue-root"></div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-tech-myview.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,5 @@ | |||||
| {{template "base/head_home" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-tech-repoview.css?v={{MD5 AppVer}}" /> | |||||
| <div id="__vue-root"></div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-tech-repoview.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,5 @@ | |||||
| {{template "base/head_home" .}} | |||||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-tech-techview.css?v={{MD5 AppVer}}" /> | |||||
| <div id="__vue-root"></div> | |||||
| <script src="{{StaticUrlPrefix}}/js/vp-tech-techview.js?v={{MD5 AppVer}}"></script> | |||||
| {{template "base/footer" .}} | |||||
| @@ -0,0 +1,173 @@ | |||||
| /* 科技项目汇聚功能相关 */ | |||||
| import service from '../service'; | |||||
| // 新建申请页面科技项目查询接口 no-项目编号,name-科技项目名称,institution-参与单位 | |||||
| export const getTechs = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| // 新建启智项目申请页面提交 url-启智项目地址,no-项目立项编号,institution-贡献单位,多个单位用逗号分隔 | |||||
| export const setOpenIApply = (data) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/openi', | |||||
| method: 'post', | |||||
| data: data, | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 新建启智项目申请页面提交 | |||||
| // url-启智项目地址,uid-启智项目uid,repo_name-启智项目名称,topics-关键词,description-简介, | |||||
| // no-项目立项编号,institution-贡献单位,多个单位用逗号分隔 | |||||
| export const setNoOpenIApply = (data) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/no_openi', | |||||
| method: 'post', | |||||
| data: data, | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 新建非启智项目申请页面项目路径可选的用户和组织 | |||||
| // 返回 [{id:用户ID,name:用户名字,rel_avatar_link:用户图像地址,short_name:用户短名称},...] | |||||
| export const getCreateRepoUser = () => { | |||||
| return service({ | |||||
| url: '/api/v1/user/owners', | |||||
| method: 'get', | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 项目过滤信息列表-筛选信息 type=0查询科技项目页, type=1查询启智项目页 | |||||
| // 返回 {type_name:项目类型的名称, 字符串数组,institution_name:参与单位的名称,字符串数组,execute_year:执行年份,字符串数组, | |||||
| // apply_year:申请年份,字符串数组,topic-关键词,字符串数组,project_name:科技项目名称,字符串数组} | |||||
| export const getTechFilterInfo = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/filter', | |||||
| method: 'get', | |||||
| params: { | |||||
| type: params.type, | |||||
| }, | |||||
| }); | |||||
| } | |||||
| // 按科技项目页查询功能 | |||||
| // 输入 name-科技项目名称,模糊匹配,type_name-项目类型的名称,精确匹配,institution_name-参与单位的名称,精确匹配,execute_year-执行年份,精确匹配,apply_year-申请年份,精确匹配,page,pageSize,sort | |||||
| // 返回 {"total":10,"data":[{}]} | |||||
| // data: {id:科技项目id,project_name:科技项目名称,institution:承担单位,all_institution:参与单位,apply_year:申报年份,execute_period:执行周期,repo_numer:项目成果数,repo:[]项目信息数组} | |||||
| export const getTechSearch = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/search', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| // 按启智项目页查询功能 | |||||
| // 输入 name-项目名称,模糊匹配,tech_name-科技项目名称,精确匹配,institution_name-参与单位的名称,精确匹配,topic-关键词,精确匹配,page,pageSize,sort | |||||
| // 返回 {"total":10,"data":[{}]} | |||||
| // data: {id:项目id,owner_id:项目拥有者id,name:项目名称,alias:项目别名,topics:关键词 字符串数组,description:项目简介,updated_unix:最后更新时间,institution:贡献单位} | |||||
| export const getTechOpenISearch = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/repo_search', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| // 我申请的项目列表 | |||||
| // 输入 page,pageSize | |||||
| // 返回 {"total":10,"data":[{}]} | |||||
| // data: {id:记录的id,name:启智项目名称,owner_name:启智项目拥有者名称,tech_name:科技项目名称,tech_number:科技项目编号,institution:项目承担单位, | |||||
| // execute_period:执行周期,contact:联系人,contact_phone:联系电话,contact_email:联系邮件,apply_user:申请账号,status:状态} | |||||
| export const getTechMyList = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/my', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| // 后台管理项目列表 | |||||
| // 输入 page,pageSize | |||||
| // 返回 {"total":10,"data":[{}]} | |||||
| // data: {id:记录的id,name:启智项目名称,owner_name:启智项目拥有者名称,tech_name:科技项目名称,tech_number:科技项目编号,institution:项目承担单位, | |||||
| // execute_period:执行周期,contact:联系人,contact_phone:联系电话,contact_email:联系邮件,apply_user:申请账号,status:状态} | |||||
| export const getTechAdminList = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/admin', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| // 后台管理项目列表交互(显示或隐藏) | |||||
| // 输入 type-show/hide,id:[1,2,3] | |||||
| // 返回 { code:0, message:"" } | |||||
| export const setTechAdminOperation = (data) => { | |||||
| return service({ | |||||
| url: `/api/v1/tech/admin/${data.type}`, | |||||
| method: 'post', | |||||
| data: { | |||||
| id: data.id | |||||
| }, | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 后台管理科技项目编号基本信息导入功能(科技项目管理员有权限) | |||||
| // 输入 form file | |||||
| export const setTechImportExcel = () => { | |||||
| return service({ | |||||
| url: `/api/v1/tech/basic`, | |||||
| method: 'post', | |||||
| data: {}, | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 增加科技项目管理员(管理员才有权限) | |||||
| // 输入 name:['111', '222'] | |||||
| // 返回 { code:0, message:"" } | |||||
| export const setTechAdminAdd = (data) => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/admin_add', | |||||
| method: 'post', | |||||
| data: data, | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // 判断是否是科技项目管理员 | |||||
| // 输入 name:['111', '222'] | |||||
| export const getIsTechAdmin = () => { | |||||
| return service({ | |||||
| url: '/api/v1/tech/is_admin', | |||||
| method: 'get', | |||||
| params: {}, | |||||
| }); | |||||
| } | |||||
| // ========================================================= | |||||
| // 搜索Topics q-topic | |||||
| // 返回 {topics: [{id,repo_count,topic_name,created,updated}]} | |||||
| export const getTopics = (params) => { | |||||
| return service({ | |||||
| url: '/api/v1/topics/search', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| // 检测 repo name q:名称, owner:所属者 | |||||
| export const getCheckRepoName = (params) => { | |||||
| return service({ | |||||
| url: '/repo/check_name', | |||||
| method: 'get', | |||||
| params: params, | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,365 @@ | |||||
| <template> | |||||
| <div> | |||||
| <TopHeader :menu="'admin_view'"></TopHeader> | |||||
| <div class="ui container"> | |||||
| <div class="top-container"> | |||||
| <el-checkbox class="check-toggle" v-model="allChecked" @change="allSelectChange">全选/全不选</el-checkbox> | |||||
| <el-button class="btn-agree" @click="batchOperate(true)">批量同意展示</el-button> | |||||
| <el-button class="btn-cancel" @click="batchOperate(false)">批量取消展示</el-button> | |||||
| </div> | |||||
| <div class="table-container"> | |||||
| <div class="table-title">2030科技项目管理</div> | |||||
| <div class="table-wrap"> | |||||
| <el-table ref="tableRef" border :data="tableData" style="width:100%;" v-loading="loading" stripe row-key="id"> | |||||
| <el-table-column label="" align="center" header-align="center" width="40" fixed> | |||||
| <template slot-scope="scope"> | |||||
| <el-checkbox v-model="scope.row.checked" @change="rowSelectChange(scope.row)"></el-checkbox> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column label="启智项目名称" align="center" header-align="center" fixed min-width="150"> | |||||
| <template slot-scope="scope"> | |||||
| <span v-if="scope.row.status == 5"> | |||||
| {{ `${scope.row.url.split('/')[3]}/${scope.row.url.split('/')[4]}` }} | |||||
| </span> | |||||
| <a v-else target="_blank" :href="`/${scope.row.repo_owner_name}/${scope.row.repo_name}`"> | |||||
| {{ `${scope.row.repo_owner_name}/${scope.row.repo_name}` }} | |||||
| </a> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="name" label="科技项目名称" align="center" header-align="center" fixed | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="status" label="展示状态" align="center" header-align="center" fixed> | |||||
| <template slot-scope="scope"> | |||||
| <span style="color:rgb(255, 37, 37)" | |||||
| :style="scope.row.status == 1 ? { color: 'rgb(56, 158, 13)' } : ''">{{ | |||||
| statusMap[scope.row.status] }}</span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="no" label="项目立项编号" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="institution" label="项目承担单位" align="center" header-align="center" | |||||
| min-width="160"></el-table-column> | |||||
| <el-table-column prop="province" label="所属省(省市)" align="center" header-align="center" | |||||
| min-width="100"></el-table-column> | |||||
| <el-table-column prop="category" label="单位性质" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="recommend" label="推荐单位" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="owner" label="项目负责人" align="center" header-align="center" | |||||
| min-width="100"></el-table-column> | |||||
| <el-table-column prop="phone" label="负责人电话" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="email" label="负责人邮箱" align="center" header-align="center" | |||||
| min-width="150"></el-table-column> | |||||
| <el-table-column prop="contact" label="项目联系人" align="center" header-align="center" | |||||
| min-width="100"></el-table-column> | |||||
| <el-table-column prop="contact_phone" label="联系人电话" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="contact_email" label="联系人邮箱" align="center" header-align="center" | |||||
| min-width="150"></el-table-column> | |||||
| <el-table-column prop="apply_year" label="申报年度" align="center" header-align="center" | |||||
| min-width="80"></el-table-column> | |||||
| <el-table-column prop="execute_month" label="执行周期(月)" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="execute_period" label="执行期限" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="type" label="项目类型" align="center" header-align="center" | |||||
| min-width="100"></el-table-column> | |||||
| <el-table-column prop="start_up" label="启动会时间" align="center" header-align="center" | |||||
| min-width="100"></el-table-column> | |||||
| <el-table-column prop="number_topic" label="课题数量" align="center" header-align="center" | |||||
| min-width="80"></el-table-column> | |||||
| <el-table-column prop="topic1" label="课题一级名称及承担单位" align="center" header-align="center" | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="topic2" label="课题二级名称及承担单位" align="center" header-align="center" | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="topic3" label="课题三级名称及承担单位" align="center" header-align="center" | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="topic4" label="课题四级名称及承担单位" align="center" header-align="center" | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="topic5" label="课题五级名称及承担单位" align="center" header-align="center" | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="all_institution" label="所有参与单位" align="center" header-align="center" | |||||
| min-width="300"></el-table-column> | |||||
| <el-table-column prop="contribution_institution" label="成果贡献单位" align="center" header-align="center" | |||||
| min-width="300"></el-table-column> | |||||
| <el-table-column prop="user_name" label="申请账号" align="center" header-align="center" width="120"> | |||||
| <template slot-scope="scope"> | |||||
| <a target="_blank" :href="`/${scope.row.user_name}`">{{ | |||||
| `${scope.row.user_name}` }}</a> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="" label="操作" align="center" header-align="center" fixed="right" width="100"> | |||||
| <template slot-scope="scope"> | |||||
| <span v-if="scope.row.status == 2" class="op-btn agree" @click="toggleStatus(scope.row)"> | |||||
| <i class="el-icon-check"></i> | |||||
| <span>同意展示</span> | |||||
| </span> | |||||
| <span v-if="scope.row.status == 1" class="op-btn cancel" @click="toggleStatus(scope.row)"> | |||||
| <i class="el-icon-close"></i> | |||||
| <span>取消展示</span> | |||||
| </span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| </div> | |||||
| <div class="op-tips"> | |||||
| <i class="el-icon-info"></i> | |||||
| 提示:批量操作只会对【展示状态】为 “未展示” 或 “已展示” 的数据生效。 | |||||
| </div> | |||||
| <div class="center"> | |||||
| <el-pagination ref="paginationRef" background @current-change="currentChange" @size-change="sizeChange" | |||||
| :current-page.sync="page" :page-sizes="pageSizes" :page-size.sync="pageSize" | |||||
| layout="total, sizes, prev, pager, next, jumper" :total="total"> | |||||
| </el-pagination> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import TopHeader from '../components/TopHeader.vue'; | |||||
| import { getTechAdminList, setTechAdminOperation } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| allChecked: false, | |||||
| loading: false, | |||||
| tableData: [], | |||||
| page: 1, | |||||
| pageSizes: [15, 30, 50, 100], | |||||
| pageSize: 50, | |||||
| total: 0, | |||||
| statusMap: { | |||||
| '1': '已展示', | |||||
| '2': '未展示', | |||||
| '3': '项目迁移中', | |||||
| '4': '项目迁移失败', | |||||
| '5': '项目不存在', | |||||
| }, | |||||
| }; | |||||
| }, | |||||
| components: { TopHeader }, | |||||
| methods: { | |||||
| getData() { | |||||
| this.loading = true; | |||||
| getTechAdminList({ | |||||
| page: this.page, | |||||
| pageSize: this.pageSize, | |||||
| }).then(res => { | |||||
| this.loading = false; | |||||
| const { total, data } = res.data; | |||||
| this.tableData = data.map((item, index) => { | |||||
| return { | |||||
| checked: false, | |||||
| ...item, | |||||
| } | |||||
| }); | |||||
| this.allChecked = false; | |||||
| this.total = total; | |||||
| }).catch(err => { | |||||
| this.loading = false; | |||||
| console.log(err); | |||||
| }); | |||||
| }, | |||||
| allSelectChange() { | |||||
| for (let i = 0, iLen = this.tableData.length; i < iLen; i++) { | |||||
| this.tableData[i].checked = this.allChecked; | |||||
| } | |||||
| this.tableData.splice(1, 0); | |||||
| }, | |||||
| rowSelectChange(row) { | |||||
| if (row.checked == false) { | |||||
| this.allChecked = false; | |||||
| } else { | |||||
| if (this.tableData.filter((item) => item.checked).length == this.tableData.length) { | |||||
| this.allChecked = true; | |||||
| } | |||||
| } | |||||
| this.tableData.splice(1, 0); | |||||
| }, | |||||
| currentChange(page) { | |||||
| this.page = page; | |||||
| this.getData(); | |||||
| }, | |||||
| sizeChange(pageSize) { | |||||
| this.pageSize = pageSize; | |||||
| this.getData(); | |||||
| }, | |||||
| toggleStatus(row) { | |||||
| if (row.status != 1 && row.status != 2) return; | |||||
| setTechAdminOperation({ | |||||
| type: row.status == 1 ? 'hide' : 'show', | |||||
| id: [row.id], | |||||
| }).then(res => { | |||||
| res = res.data; | |||||
| if (res.Code == 0) { | |||||
| this.$message({ | |||||
| type: 'success', | |||||
| message: this.$t('submittedSuccessfully'), | |||||
| }); | |||||
| row.status = row.status == 1 ? 2 : 1; | |||||
| } else { | |||||
| this.$message({ | |||||
| type: 'error', | |||||
| message: this.$t('submittedFailed'), | |||||
| }); | |||||
| } | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.$message({ | |||||
| type: 'error', | |||||
| message: this.$t('submittedFailed'), | |||||
| }); | |||||
| }); | |||||
| }, | |||||
| batchOperate(showOr) { | |||||
| const selectedData = this.tableData.filter((item) => { | |||||
| return item.checked && item.status == (showOr ? 2 : 1); | |||||
| }); | |||||
| if (!selectedData.length) { | |||||
| this.$message({ | |||||
| type: 'info', | |||||
| message: '请选择符合条件的数据进行操作!', | |||||
| }); | |||||
| return; | |||||
| } | |||||
| setTechAdminOperation({ | |||||
| type: showOr ? 'show' : 'hide', | |||||
| id: selectedData.map(item => item.id), | |||||
| }).then(res => { | |||||
| res = res.data; | |||||
| if (res.Code == 0) { | |||||
| this.$message({ | |||||
| type: 'success', | |||||
| message: this.$t('submittedSuccessfully'), | |||||
| }); | |||||
| this.getData(); | |||||
| } else { | |||||
| this.$message({ | |||||
| type: 'error', | |||||
| message: this.$t('submittedFailed'), | |||||
| }); | |||||
| } | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.$message({ | |||||
| type: 'error', | |||||
| message: this.$t('submittedFailed'), | |||||
| }); | |||||
| }); | |||||
| }, | |||||
| }, | |||||
| beforeMount() { | |||||
| this.getData(); | |||||
| }, | |||||
| mounted() { }, | |||||
| beforeDestroy() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .top-container { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin: 30px 0 15px 0; | |||||
| .check-toggle { | |||||
| margin-right: 20px; | |||||
| } | |||||
| .btn-agree { | |||||
| margin-right: 10px; | |||||
| background: rgb(50, 145, 248); | |||||
| color: rgb(255, 255, 255); | |||||
| &:hover { | |||||
| opacity: 0.9; | |||||
| } | |||||
| &:active { | |||||
| opacity: 0.8; | |||||
| } | |||||
| } | |||||
| .btn-cancel { | |||||
| margin-right: 10px; | |||||
| background: rgb(250, 140, 22); | |||||
| color: rgb(255, 255, 255); | |||||
| &:hover { | |||||
| opacity: 0.9; | |||||
| } | |||||
| &:active { | |||||
| opacity: 0.8; | |||||
| } | |||||
| } | |||||
| } | |||||
| .table-container { | |||||
| .table-title { | |||||
| height: 43px; | |||||
| font-size: 16px; | |||||
| font-weight: 700; | |||||
| padding: 15px; | |||||
| color: rgb(16, 16, 16); | |||||
| border-color: rgb(212, 212, 213); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| border-radius: 5px 5px 0px 0px; | |||||
| background: rgb(240, 240, 240); | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| .table-wrap { | |||||
| overflow-x: auto; | |||||
| /deep/ .el-table__header { | |||||
| th { | |||||
| background: rgb(249, 249, 249); | |||||
| font-size: 12px; | |||||
| color: rgb(136, 136, 136); | |||||
| font-weight: normal; | |||||
| } | |||||
| } | |||||
| /deep/ .el-table__body { | |||||
| td { | |||||
| font-size: 12px; | |||||
| } | |||||
| } | |||||
| /deep/ .el-radio__label { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| .op-tips { | |||||
| margin: 10px 0 10px 0; | |||||
| color: #606266; | |||||
| font-size: 12px; | |||||
| i { | |||||
| margin-right: 4px; | |||||
| } | |||||
| } | |||||
| .op-btn { | |||||
| cursor: pointer; | |||||
| &.agree { | |||||
| color: rgb(56, 158, 13); | |||||
| } | |||||
| &.cancel { | |||||
| color: rgb(50, 145, 248); | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -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'); | |||||
| @@ -0,0 +1,289 @@ | |||||
| <template> | |||||
| <div> | |||||
| <div class="filter-main" v-show="!showSecond"> | |||||
| <div class="filter-c" v-for="(item, index) in mainData"> | |||||
| <div class="filter-title">{{ item.title }}</div> | |||||
| <div class="filter-item-c"> | |||||
| <div class="filter-item" v-for="(_item, _index) in item.showData" @click="changeFilter(item, _item)" | |||||
| :style="conds[item.key] == _item ? { backgroundColor: item.focusBgColor, color: item.focusColor } : { backgroundColor: item.bgColor, color: item.color }"> | |||||
| {{ _item }} | |||||
| </div> | |||||
| </div> | |||||
| <div class="filter-view-more-c" v-if="item.data.length > item.showMaxLen"> | |||||
| <span class="filter-view-more" @click="goMore(item)"> | |||||
| <i class="el-icon-arrow-down"></i><span>展开更多</span> | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="filter-second" v-show="showSecond"> | |||||
| <div class="filter-second-hd"> | |||||
| <span class="filter-second-go-back" @click="goBack()"> | |||||
| <i class="el-icon-back"></i><span>返回上一级</span> | |||||
| </span> | |||||
| </div> | |||||
| <div class="filter-title">{{ secondData.title }}</div> | |||||
| <div class="filter-search"> | |||||
| <el-input placeholder="搜索" prefix-icon="el-icon-search" v-model="searchKeyword" clearable | |||||
| @input="searchFilterItem"> | |||||
| </el-input> | |||||
| </div> | |||||
| <div class="filter-item-c"> | |||||
| <div class="filter-item" v-for="(_item, _index) in secondData.showData" @click="changeFilter(secondData, _item)" | |||||
| :style="conds[secondData.key] == _item ? { backgroundColor: secondData.focusBgColor, color: secondData.focusColor } : { backgroundColor: secondData.bgColor, color: secondData.color }"> | |||||
| {{ _item }} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { getTechFilterInfo } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| name: "Filters", | |||||
| props: { | |||||
| type: { type: Number, default: -1 }, // 0-tech_view, 1-repo_view | |||||
| condition: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: {}, | |||||
| data() { | |||||
| return { | |||||
| list_tech: [{ | |||||
| title: '项目类型', | |||||
| key: 'type_name', | |||||
| bgColor: 'rgb(237, 234, 251)', | |||||
| color: 'rgb(100, 59, 159)', | |||||
| focusBgColor: 'rgb(100, 59, 159)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 6, | |||||
| }, { | |||||
| title: '项目参与单位', | |||||
| key: 'institution_name', | |||||
| bgColor: 'rgb(234, 241, 251)', | |||||
| color: 'rgb(18, 76, 157)', | |||||
| focusBgColor: 'rgb(18, 76, 157)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 10, | |||||
| }, { | |||||
| title: '执行周期包含年份', | |||||
| key: 'execute_year', | |||||
| bgColor: 'rgb(225, 242, 234)', | |||||
| color: 'rgb(8, 96, 96)', | |||||
| focusBgColor: 'rgb(8, 96, 96)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 20, | |||||
| }, { | |||||
| title: '申报年份', | |||||
| key: 'apply_year', | |||||
| bgColor: 'rgb(231, 249, 222)', | |||||
| color: 'rgb(55, 94, 2)', | |||||
| focusBgColor: 'rgb(55, 94, 2)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 20, | |||||
| }], | |||||
| list_repo: [{ | |||||
| title: '关键词', | |||||
| key: 'topic', | |||||
| bgColor: 'rgb(234, 250, 251)', | |||||
| color: 'rgb(0, 167, 132)', | |||||
| focusBgColor: 'rgb(0, 167, 132)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 10, | |||||
| }, { | |||||
| title: '所属科技项目', | |||||
| key: 'project_name', | |||||
| bgColor: 'rgb(234, 245, 251)', | |||||
| color: 'rgb(8, 0, 148)', | |||||
| focusBgColor: 'rgb(8, 0, 148)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 8, | |||||
| }, { | |||||
| title: '成果贡献单位', | |||||
| key: 'institution_name', | |||||
| bgColor: 'rgb(234, 241, 251)', | |||||
| color: 'rgb(18, 76, 157)', | |||||
| focusBgColor: 'rgb(18, 76, 157)', | |||||
| focusColor: 'rgb(255, 255, 255)', | |||||
| data: [], | |||||
| showData: [], | |||||
| showMaxLen: 10, | |||||
| }, | |||||
| ], | |||||
| conds: { | |||||
| type_name: '', | |||||
| institution_name: '', | |||||
| execute_year: '', | |||||
| apply_year: '', | |||||
| topic: '', | |||||
| project_name: '', | |||||
| }, | |||||
| mainData: [], | |||||
| showSecond: false, | |||||
| secondData: {}, | |||||
| searchKeyword: '', | |||||
| }; | |||||
| }, | |||||
| methods: { | |||||
| goMore(item) { | |||||
| this.secondData = item; | |||||
| this.searchKeyword = ''; | |||||
| this.secondData.showData = item.data; | |||||
| this.showSecond = true; | |||||
| }, | |||||
| goBack() { | |||||
| this.showSecond = false; | |||||
| }, | |||||
| changeFilter(item, _item) { | |||||
| const value = this.conds[item.key] == _item ? '' : _item; | |||||
| this.$emit('changeCondition', { | |||||
| [item.key]: value | |||||
| }); | |||||
| }, | |||||
| searchFilterItem() { | |||||
| const keyword = this.searchKeyword.trim().toLocaleLowerCase(); | |||||
| this.secondData.showData = this.secondData.data.filter(item => { | |||||
| return item.toString().toLocaleLowerCase().indexOf(keyword) >= 0; | |||||
| }); | |||||
| }, | |||||
| }, | |||||
| watch: { | |||||
| condition: { | |||||
| handler(newVal) { | |||||
| this.conds.type_name = newVal.type_name || ''; | |||||
| this.conds.institution_name = newVal.institution_name || ''; | |||||
| this.conds.execute_year = newVal.execute_year || ''; | |||||
| this.conds.apply_year = newVal.apply_year || ''; | |||||
| this.conds.topic = newVal.topic || ''; | |||||
| this.conds.project_name = newVal.project_name || ''; | |||||
| }, | |||||
| immediate: true, | |||||
| deep: true, | |||||
| }, | |||||
| }, | |||||
| beforeMount() { | |||||
| if (this.type == 0) { | |||||
| this.mainData = this.list_tech; | |||||
| } else if (this.type == 1) { | |||||
| this.mainData = this.list_repo; | |||||
| } | |||||
| getTechFilterInfo({ | |||||
| type: this.type, | |||||
| }).then(res => { | |||||
| const data = res.data; | |||||
| if (data) { | |||||
| for (let i = 0, iLen = this.mainData.length; i < iLen; i++) { | |||||
| const filterItem = this.mainData[i]; | |||||
| const key = filterItem.key; | |||||
| const max = filterItem.showMaxLen; | |||||
| if (data[key]) { | |||||
| filterItem.data = data[key].map(item => item.toString()); | |||||
| filterItem.showData = filterItem.data.slice(0, max); | |||||
| if (this.conds[key] && filterItem.showData.indexOf(this.conds[key]) < 0) { | |||||
| filterItem.showData.push(this.conds[key]); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| }); | |||||
| }, | |||||
| mounted() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .filter-c { | |||||
| margin-bottom: 32px; | |||||
| .filter-title { | |||||
| font-size: 18px; | |||||
| color: rgb(16, 16, 16); | |||||
| margin: 14px 0; | |||||
| } | |||||
| .filter-item-c { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| .filter-item { | |||||
| border-radius: 3px; | |||||
| padding: 3px 10px; | |||||
| margin-right: 10px; | |||||
| margin-bottom: 10px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| .filter-view-more-c { | |||||
| margin-top: 6px; | |||||
| .filter-view-more { | |||||
| color: rgb(50, 145, 248); | |||||
| font-size: 14px; | |||||
| cursor: pointer; | |||||
| i { | |||||
| margin-right: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .filter-second { | |||||
| .filter-second-hd { | |||||
| margin: 14px 0 24px 0; | |||||
| .filter-second-go-back { | |||||
| color: rgb(50, 145, 248); | |||||
| font-size: 14px; | |||||
| cursor: pointer; | |||||
| i { | |||||
| margin-right: 4px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .filter-title { | |||||
| font-size: 18px; | |||||
| color: rgb(16, 16, 16); | |||||
| margin: 14px 0; | |||||
| } | |||||
| .filter-search { | |||||
| margin: 5px 10px 10px 0; | |||||
| } | |||||
| .filter-item-c { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| max-height: 605px; | |||||
| overflow-y: auto; | |||||
| .filter-item { | |||||
| border-radius: 3px; | |||||
| padding: 3px 10px; | |||||
| margin-right: 10px; | |||||
| margin-bottom: 10px; | |||||
| cursor: pointer; | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,158 @@ | |||||
| <template> | |||||
| <div class="item"> | |||||
| <div class="title-c"> | |||||
| <div class="avatar-c"> | |||||
| <img v-if="data.rel_avatar_link" class="avatar" :src="data.rel_avatar_link" /> | |||||
| <img v-else class="avatar" :avatar="data.owner_name" /> | |||||
| </div> | |||||
| <div class="title"> | |||||
| <a target="_blank" :href="`/${data.owner_name}/${data.name}`"> | |||||
| <span :title="data.alias">{{ data.alias }}</span> | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| <div class="descr" :title="data.description"> | |||||
| {{ data.description }} | |||||
| </div> | |||||
| <div class="topics"> | |||||
| <a v-for="(item, index) in data.topics" :key="index" class="topic" target="_blank" | |||||
| :href="`/explore/repos?q=&topic=${item}&sort=hot`">{{ item }}</a> | |||||
| </div> | |||||
| <div class="footer"> | |||||
| <div class="contractor" :title="data.institution.split(',').join('、')">{{ data.institution.split(',').join('、') }} | |||||
| </div> | |||||
| <div class="update-time"> | |||||
| <span>{{ $t('repos.updated') }}</span> | |||||
| <el-tooltip effect="dark" :content="dateFormat(data.updated_unix)" placement="top-start"> | |||||
| <span>{{ calcFromNow(data.updated_unix) }}</span> | |||||
| </el-tooltip> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import dayjs from 'dayjs'; | |||||
| import { lang } from '~/langs'; | |||||
| import { timeSinceUnix } from '~/utils'; | |||||
| export default { | |||||
| name: "PrjResultsItem", | |||||
| props: { | |||||
| data: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: {}, | |||||
| data() { | |||||
| return {}; | |||||
| }, | |||||
| methods: { | |||||
| calcFromNow(unix) { | |||||
| 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 { | |||||
| border-color: rgb(232, 224, 236); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| box-shadow: rgba(168, 157, 226, 0.2) 0px 5px 10px 0px; | |||||
| 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.01900000000000005%2C%200.997%2C%20-0.12862587255310284%2C%20-0.01900000000000005%2C%200.995%2C%200.014)%22%3E%3Cstop%20stop-color%3D%22%23f2edf5%22%20stop-opacity%3D%221%22%20offset%3D%220.01%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%220.31%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"); | |||||
| overflow: hidden; | |||||
| padding: 15px; | |||||
| } | |||||
| .title-c { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .avatar-c { | |||||
| height: 28px; | |||||
| width: 28px; | |||||
| margin-right: 6px; | |||||
| img { | |||||
| border-radius: 100%; | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| } | |||||
| } | |||||
| .title { | |||||
| flex: 1; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| span { | |||||
| color: rgb(16, 16, 16); | |||||
| font-size: 16px; | |||||
| font-weight: 400; | |||||
| } | |||||
| } | |||||
| } | |||||
| .descr { | |||||
| height: 40px; | |||||
| margin-top: 8px; | |||||
| font-size: 12px; | |||||
| font-weight: 300; | |||||
| color: rgb(136, 136, 136); | |||||
| text-overflow: ellipsis; | |||||
| word-break: break-all; | |||||
| display: -webkit-box; | |||||
| -webkit-box-orient: vertical; | |||||
| -webkit-line-clamp: 2; | |||||
| max-height: 41px; | |||||
| overflow: hidden; | |||||
| } | |||||
| .topics { | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| margin-top: 5px; | |||||
| height: 20px; | |||||
| .topic { | |||||
| color: rgba(16, 16, 16, 0.8); | |||||
| border-radius: 4px; | |||||
| font-size: 12px; | |||||
| background: rgba(232, 232, 232, 0.6); | |||||
| padding: 2px 6px; | |||||
| margin-right: 8px; | |||||
| } | |||||
| } | |||||
| .footer { | |||||
| margin-top: 12px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| height: 20px; | |||||
| .contractor { | |||||
| font-size: 14px; | |||||
| font-weight: 400; | |||||
| color: rgba(0, 40, 192, 0.73); | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .update-time { | |||||
| text-align: right; | |||||
| min-width: 140px; | |||||
| font-size: 12px; | |||||
| font-weight: 300; | |||||
| color: rgb(136, 136, 136); | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,154 @@ | |||||
| <template> | |||||
| <div class="list-container"> | |||||
| <div style="min-height:540px;"> | |||||
| <div class="list-item-container" v-loading="loading"> | |||||
| <div class="item-container" v-for="(item, index) in list" :key="item.ID"> | |||||
| <PrjResultsItem :data="item"></PrjResultsItem> | |||||
| </div> | |||||
| </div> | |||||
| <div v-show="(!list.length && !loading)" class="no-data"> | |||||
| <div class="item-empty"> | |||||
| <div class="item-empty-icon bgtask-header-pic"></div> | |||||
| <div class="item-empty-tips">没有找到相关的项目</div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="center" v-show="list.length"> | |||||
| <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 PrjResultsItem from './PrjResultsItem.vue'; | |||||
| import LetterAvatar from '~/utils/letteravatar'; | |||||
| import { getTechOpenISearch } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| name: "PrjResultsList", | |||||
| props: { | |||||
| condition: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: { PrjResultsItem }, | |||||
| data() { | |||||
| return { | |||||
| loading: false, | |||||
| list: [], | |||||
| iPageSizes: [15, 30, 50], | |||||
| iPageSize: 15, | |||||
| iPage: 1, | |||||
| total: 0, | |||||
| }; | |||||
| }, | |||||
| methods: { | |||||
| getListData() { | |||||
| this.loading = true; | |||||
| getTechOpenISearch({ | |||||
| name: this.condition.q, | |||||
| tech_name: this.condition.project_name, | |||||
| institution_name: this.condition.institution_name, | |||||
| topic: this.condition.topic, | |||||
| page: this.condition.page, | |||||
| pageSize: this.condition.pageSize, | |||||
| sort: this.condition.sort, | |||||
| }).then(res => { | |||||
| res = res.data; | |||||
| this.loading = false; | |||||
| this.total = res.total || 0; | |||||
| this.list = res.data || []; | |||||
| this.$nextTick(() => { | |||||
| LetterAvatar.transform(); | |||||
| }); | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.loading = false; | |||||
| this.list = []; | |||||
| this.total = 0; | |||||
| }); | |||||
| }, | |||||
| search() { | |||||
| this.getListData(); | |||||
| }, | |||||
| currentChange(page) { | |||||
| this.iPage = page; | |||||
| this.$emit('changeCondition', { | |||||
| page: this.iPage, | |||||
| pageSize: this.iPageSize, | |||||
| changePage: true, | |||||
| }); | |||||
| }, | |||||
| sizeChange(pageSize) { | |||||
| this.iPageSize = pageSize; | |||||
| this.$emit('changeCondition', { | |||||
| page: this.iPage, | |||||
| pageSize: this.iPageSize, | |||||
| }); | |||||
| }, | |||||
| }, | |||||
| watch: { | |||||
| condition: { | |||||
| handler(newVal) { | |||||
| this.iPage = newVal.page; | |||||
| this.iPageSize = newVal.pageSize; | |||||
| }, | |||||
| immediate: true, | |||||
| deep: true, | |||||
| }, | |||||
| }, | |||||
| mounted() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .list-container { | |||||
| .list-item-container { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| .item-container { | |||||
| width: 50%; | |||||
| padding: 12px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .center { | |||||
| text-align: center; | |||||
| } | |||||
| .no-data { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| padding: 0 12px; | |||||
| .item-empty { | |||||
| height: 180px; | |||||
| width: 100%; | |||||
| padding: 12px; | |||||
| border-color: rgb(232, 224, 236); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| box-shadow: rgba(168, 157, 226, 0.2) 0px 5px 10px 0px; | |||||
| 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.01900000000000005%2C%200.997%2C%20-0.06169646324801269%2C%20-0.01900000000000005%2C%200.995%2C%200.014)%22%3E%3Cstop%20stop-color%3D%22%23f2edf5%22%20stop-opacity%3D%221%22%20offset%3D%220.01%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%220.31%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; | |||||
| flex-direction: column; | |||||
| justify-content: center; | |||||
| .item-empty-icon { | |||||
| height: 80px; | |||||
| width: 100%; | |||||
| } | |||||
| .item-empty-tips { | |||||
| font-size: 16px; | |||||
| color: rgb(16, 16, 16); | |||||
| text-align: center; | |||||
| margin-top: 2px; | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,248 @@ | |||||
| <template> | |||||
| <div class="item"> | |||||
| <div class="header"> | |||||
| <div class="header-l"> | |||||
| <div class="title" :title="data.project_name">{{ data.project_name }}</div> | |||||
| <div class="prj-type">{{ data.type }}</div> | |||||
| </div> | |||||
| <div class="header-r"> | |||||
| <a :href="`/tech/repo_view?project_name=${data.project_name}`"> | |||||
| <span>更多成果</span> | |||||
| <i class="el-icon-arrow-right"></i> | |||||
| </a> | |||||
| </div> | |||||
| </div> | |||||
| <div class="content"> | |||||
| <div class="item-l-c"> | |||||
| <div class="row"> | |||||
| <span class="tit">负责单位:</span> | |||||
| <span class="val" :title="data.institution">{{ data.institution }}</span> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <span class="tit">参与单位:</span> | |||||
| <span class="val" :title="data.all_institution.split(',').join('、')">{{ | |||||
| data.all_institution.split(',').join('、') | |||||
| }}</span> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <span class="tit">申报年份:</span> | |||||
| <span class="val">{{ data.apply_year }}</span> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <span class="tit">执行周期:</span> | |||||
| <span class="val">{{ data.execute_period }}</span> | |||||
| </div> | |||||
| <div class="row"> | |||||
| <span class="tit">项目成果数:</span> | |||||
| <span class="val">{{ data.repo_numer }}</span> | |||||
| </div> | |||||
| </div> | |||||
| <div class="item-r-c"> | |||||
| <a class="repo-item-c" target="_blank" :href="`/${item.owner_name}/${item.name}`" | |||||
| v-for="(item, index) in data.Repos"> | |||||
| <div class="repo-item"> | |||||
| <div class="repo-hd"> | |||||
| <div class="repo-avatar"> | |||||
| <img v-if="data.rel_avatar_link" class="avatar" :src="data.rel_avatar_link" /> | |||||
| <img v-else class="avatar" :avatar="item.owner_name" /> | |||||
| </div> | |||||
| <div class="repo-tit">{{ item.alias }}</div> | |||||
| </div> | |||||
| <div class="repo-content"> | |||||
| <div class="repo-descr"> | |||||
| {{ item.description }} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </a> | |||||
| <div class="repo-item-c" v-if="!data.Repos.length"> | |||||
| <div class="bgtask-header-pic" style="margin-top:-20px;"></div> | |||||
| <div class="repo-item" style="text-align:center;">项目成果待展示</div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import dayjs from 'dayjs'; | |||||
| import { lang } from '~/langs'; | |||||
| import { timeSinceUnix } from '~/utils'; | |||||
| export default { | |||||
| name: "SciAndTechPrjItem", | |||||
| props: { | |||||
| data: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: {}, | |||||
| data() { | |||||
| return {}; | |||||
| }, | |||||
| methods: { | |||||
| calcFromNow(unix) { | |||||
| 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 { | |||||
| border-color: rgb(232, 224, 236); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| box-shadow: rgba(168, 157, 226, 0.2) 0px 5px 10px 0px; | |||||
| 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.01900000000000005%2C%200.997%2C%20-0.12862587255310284%2C%20-0.01900000000000005%2C%200.995%2C%200.014)%22%3E%3Cstop%20stop-color%3D%22%23f2edf5%22%20stop-opacity%3D%221%22%20offset%3D%220.01%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%220.31%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"); | |||||
| overflow: hidden; | |||||
| padding: 15px; | |||||
| .header { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| .header-l { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .title { | |||||
| color: rgb(16, 16, 16); | |||||
| font-size: 18px; | |||||
| font-weight: bold; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| max-width: 800px; | |||||
| } | |||||
| .prj-type { | |||||
| margin-left: 14px; | |||||
| font-size: 12px; | |||||
| font-weight: 400; | |||||
| color: rgb(100, 59, 159); | |||||
| background: rgb(237, 234, 251); | |||||
| padding: 2px 5px; | |||||
| border-radius: 4px; | |||||
| } | |||||
| } | |||||
| .header-r { | |||||
| display: flex; | |||||
| align-items: baseline; | |||||
| text-align: right; | |||||
| color: rgb(50, 145, 248); | |||||
| font-size: 12px; | |||||
| width: 80px; | |||||
| justify-content: flex-end; | |||||
| i { | |||||
| margin-left: 1px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .content { | |||||
| display: flex; | |||||
| margin-top: 10px; | |||||
| .item-l-c { | |||||
| flex: 1; | |||||
| .row { | |||||
| display: flex; | |||||
| font-size: 14px; | |||||
| color: rgb(16, 16, 16); | |||||
| margin: 8px 0; | |||||
| font-weight: 400; | |||||
| .tit { | |||||
| color: rgba(136, 136, 136, 1); | |||||
| } | |||||
| .val { | |||||
| flex: 1; | |||||
| width: 0; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| } | |||||
| &:last-child { | |||||
| margin-bottom: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| .item-r-c { | |||||
| flex: 2; | |||||
| margin-left: 10px; | |||||
| display: flex; | |||||
| .repo-item-c { | |||||
| display: block; | |||||
| flex: 1; | |||||
| margin: 10px; | |||||
| padding: 20px; | |||||
| border-color: rgba(157, 197, 226, 0.4); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| border-radius: 5px; | |||||
| box-shadow: rgba(157, 197, 226, 0.2) 0px 5px 10px 0px; | |||||
| background: rgb(255, 255, 255); | |||||
| width: 0; | |||||
| .repo-item { | |||||
| .repo-hd { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .repo-avatar { | |||||
| width: 28px; | |||||
| height: 28px; | |||||
| img { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| border-radius: 100%; | |||||
| } | |||||
| } | |||||
| .repo-tit { | |||||
| flex: 1; | |||||
| margin-left: 8px; | |||||
| width: 0; | |||||
| font-size: 14px; | |||||
| color: rgb(50, 145, 248); | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| } | |||||
| } | |||||
| .repo-content { | |||||
| margin-top: 10px; | |||||
| .repo-descr { | |||||
| width: 100%; | |||||
| font-size: 12px; | |||||
| color: rgb(136, 136, 136); | |||||
| text-overflow: ellipsis; | |||||
| word-break: break-all; | |||||
| display: -webkit-box; | |||||
| -webkit-box-orient: vertical; | |||||
| -webkit-line-clamp: 2; | |||||
| max-height: 41px; | |||||
| overflow: hidden; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,150 @@ | |||||
| <template> | |||||
| <div class="list-container"> | |||||
| <div class="list-item-container" style="min-height:540px;" v-loading="loading"> | |||||
| <div class="item-container" v-for="(item, index) in list" :key="item.ID"> | |||||
| <SciAndTechPrjItem :data="item"></SciAndTechPrjItem> | |||||
| </div> | |||||
| <div v-show="(!list.length && !loading)" class="no-data"> | |||||
| <div class="item-empty"> | |||||
| <div class="item-empty-icon bgtask-header-pic"></div> | |||||
| <div class="item-empty-tips">没有找到相关的科技项目</div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="center" v-show="list.length"> | |||||
| <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 SciAndTechPrjItem from './SciAndTechPrjItem.vue'; | |||||
| import LetterAvatar from '~/utils/letteravatar'; | |||||
| import { getTechSearch } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| name: "SciAndTechPrjList", | |||||
| props: { | |||||
| condition: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: { SciAndTechPrjItem }, | |||||
| data() { | |||||
| return { | |||||
| loading: false, | |||||
| list: [], | |||||
| iPageSizes: [15, 30, 50], | |||||
| iPageSize: 15, | |||||
| iPage: 1, | |||||
| total: 0, | |||||
| }; | |||||
| }, | |||||
| methods: { | |||||
| getListData() { | |||||
| this.loading = true; | |||||
| getTechSearch({ | |||||
| name: this.condition.q, | |||||
| type_name: this.condition.type_name, | |||||
| institution_name: this.condition.institution_name, | |||||
| execute_year: this.condition.execute_year, | |||||
| apply_year: this.condition.apply_year, | |||||
| sort: this.condition.sort, | |||||
| page: this.condition.page, | |||||
| pageSize: this.condition.pageSize, | |||||
| }).then(res => { | |||||
| res = res.data; | |||||
| this.loading = false; | |||||
| this.total = res.total || 0; | |||||
| this.list = res.data; | |||||
| this.$nextTick(() => { | |||||
| LetterAvatar.transform(); | |||||
| }); | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.loading = false; | |||||
| this.list = []; | |||||
| this.total = 0; | |||||
| }); | |||||
| }, | |||||
| search() { | |||||
| this.getListData(); | |||||
| }, | |||||
| currentChange(page) { | |||||
| this.iPage = page; | |||||
| this.$emit('changeCondition', { | |||||
| page: this.iPage, | |||||
| pageSize: this.iPageSize, | |||||
| changePage: true, | |||||
| }); | |||||
| }, | |||||
| sizeChange(pageSize) { | |||||
| this.iPageSize = pageSize; | |||||
| this.$emit('changeCondition', { | |||||
| page: this.iPage, | |||||
| pageSize: this.iPageSize, | |||||
| }); | |||||
| }, | |||||
| }, | |||||
| watch: { | |||||
| condition: { | |||||
| handler(newVal) { | |||||
| this.iPage = newVal.page; | |||||
| this.iPageSize = newVal.pageSize; | |||||
| }, | |||||
| immediate: true, | |||||
| deep: true, | |||||
| }, | |||||
| }, | |||||
| mounted() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .list-container { | |||||
| .list-item-container { | |||||
| .item-container { | |||||
| width: 100%; | |||||
| padding: 12px; | |||||
| } | |||||
| } | |||||
| } | |||||
| .center { | |||||
| text-align: center; | |||||
| } | |||||
| .no-data { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| padding: 0 12px; | |||||
| .item-empty { | |||||
| height: 205px; | |||||
| width: 100%; | |||||
| padding: 12px; | |||||
| border-color: rgb(232, 224, 236); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| box-shadow: rgba(168, 157, 226, 0.2) 0px 5px 10px 0px; | |||||
| 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.01900000000000005%2C%200.997%2C%20-0.06169646324801269%2C%20-0.01900000000000005%2C%200.995%2C%200.014)%22%3E%3Cstop%20stop-color%3D%22%23f2edf5%22%20stop-opacity%3D%221%22%20offset%3D%220.01%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%220.31%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; | |||||
| flex-direction: column; | |||||
| justify-content: center; | |||||
| .item-empty-icon { | |||||
| height: 80px; | |||||
| width: 100%; | |||||
| } | |||||
| .item-empty-tips { | |||||
| font-size: 16px; | |||||
| color: rgb(16, 16, 16); | |||||
| text-align: center; | |||||
| margin-top: 10px; | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,191 @@ | |||||
| <template> | |||||
| <div class="search-bar"> | |||||
| <div class="search-bar-l"> | |||||
| <div class="search-c"> | |||||
| <div class="search-input-c"> | |||||
| <input type="text" :placeholder="serchPlaceHolder" v-model="keyword" @keyup.enter="search"> | |||||
| </div> | |||||
| <div class="search-btn" @click="search">{{ $t('repos.search') }}</div> | |||||
| </div> | |||||
| <el-button class="apply-btn" type="primary" icon="el-icon-plus" size="medium" @click="apply">申请展示成果</el-button> | |||||
| <div class="openi-link-c"> | |||||
| <a class="openi-link" target="_blank" href="javascript:;">OpenI启智社区开源指南</a> | |||||
| </div> | |||||
| </div> | |||||
| <div class="sort-c"> | |||||
| <el-select class="select" size="medium" v-model="sortType" @change="changeSort" placeholder="排序" clearable> | |||||
| <el-option v-for="item in sortList" :key="item.k" :label="item.v" :value="item.k" /> | |||||
| </el-select> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| name: "SearchBar", | |||||
| props: { | |||||
| type: { type: Number, default: -1 }, // 0-tech_view, 1-repo_view | |||||
| condition: { type: Object, default: () => ({}) }, | |||||
| }, | |||||
| components: {}, | |||||
| data() { | |||||
| return { | |||||
| keyword: '', | |||||
| serchPlaceHolder: '', | |||||
| sortType: '', | |||||
| techSortList: [{ | |||||
| k: 'time', | |||||
| v: this.$t('repos.recentlyUpdated'), | |||||
| }, { | |||||
| k: 'count', | |||||
| v: '项目成果数', | |||||
| }], | |||||
| repoSortList: [ | |||||
| { | |||||
| k: 'mostpopular', | |||||
| v: this.$t('repos.mostPopular'), | |||||
| }, { | |||||
| k: 'recentupdate', | |||||
| v: this.$t('repos.recentlyUpdated'), | |||||
| }, { | |||||
| k: 'newest', | |||||
| v: this.$t('repos.newest'), | |||||
| } | |||||
| ], | |||||
| sortList: [], | |||||
| isTechAdmin: false, | |||||
| }; | |||||
| }, | |||||
| methods: { | |||||
| search() { | |||||
| this.$emit('changeCondition', { | |||||
| q: this.keyword.trim() | |||||
| }); | |||||
| }, | |||||
| apply() { | |||||
| window.location.href = '/tech/new'; | |||||
| }, | |||||
| manage() { | |||||
| window.location.href = '/tech/admin_view'; | |||||
| }, | |||||
| changeSort() { | |||||
| this.$emit('changeCondition', { | |||||
| sort: this.sortType | |||||
| }); | |||||
| } | |||||
| }, | |||||
| watch: { | |||||
| condition: { | |||||
| handler(newVal) { | |||||
| this.keyword = newVal.q; | |||||
| this.sortType = newVal.sort; | |||||
| }, | |||||
| immediate: true, | |||||
| deep: true, | |||||
| }, | |||||
| }, | |||||
| beforeMount() { | |||||
| if (this.type == 0) { | |||||
| this.sortList = this.techSortList; | |||||
| this.serchPlaceHolder = '搜索科技项目名称'; | |||||
| } else if (this.type == 1) { | |||||
| this.serchPlaceHolder = '搜索项目'; | |||||
| this.sortList = this.repoSortList; | |||||
| } | |||||
| }, | |||||
| mounted() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .search-bar { | |||||
| margin: 30px 0; | |||||
| display: flex; | |||||
| justify-content: space-between; | |||||
| .search-bar-l { | |||||
| display: flex; | |||||
| .search-c { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| margin-right: 20px; | |||||
| .search-input-c { | |||||
| width: 268px; | |||||
| height: 40px; | |||||
| border-color: rgba(0, 61, 192, 0.73); | |||||
| border-width: 2px; | |||||
| border-style: solid; | |||||
| font-size: 14px; | |||||
| line-height: 20px; | |||||
| padding: 8px; | |||||
| display: flex; | |||||
| color: rgb(136, 136, 136); | |||||
| align-items: center; | |||||
| 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.014005111865831027%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%23e2d1ea%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%220.3%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"); | |||||
| input { | |||||
| width: 100%; | |||||
| border: none; | |||||
| outline: none; | |||||
| } | |||||
| } | |||||
| .search-btn { | |||||
| height: 40px; | |||||
| font-size: 14px; | |||||
| line-height: 20px; | |||||
| padding: 0px; | |||||
| display: flex; | |||||
| color: rgb(255, 255, 255); | |||||
| align-items: center; | |||||
| text-align: center; | |||||
| justify-content: center; | |||||
| padding: 0 20px; | |||||
| cursor: pointer; | |||||
| 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.1800000000000002%2C%200.9499999999999998%2C%20-0.23749999999999996%2C%20-1.1800000000000002%2C%201.024%2C%200.047)%22%3E%3Cstop%20stop-color%3D%22%23bbd2f2%22%20stop-opacity%3D%221%22%20offset%3D%220.02%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23003dc0%22%20stop-opacity%3D%220.73%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"); | |||||
| } | |||||
| } | |||||
| .apply-btn { | |||||
| margin-left: 20px; | |||||
| border-color: rgb(31, 1, 115); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| border-radius: 6px; | |||||
| font-size: 14px; | |||||
| 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.08281144868278038%2C%206.123233995736766e-17%2C%200.5%2C%200)%22%3E%3Cstop%20stop-color%3D%22%233291f8%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23060075%22%20stop-opacity%3D%220.73%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"); | |||||
| &:hover { | |||||
| opacity: 0.9; | |||||
| } | |||||
| &:active { | |||||
| opacity: 0.8; | |||||
| } | |||||
| } | |||||
| .openi-link-c { | |||||
| margin-left: 20px; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| .openi-link { | |||||
| text-decoration: underline; | |||||
| color: rgb(50, 145, 248); | |||||
| } | |||||
| } | |||||
| } | |||||
| .sort-c { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: center; | |||||
| justify-self: flex-end; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,141 @@ | |||||
| <template> | |||||
| <div> | |||||
| <div class="bg-container"> | |||||
| <div class="bg"> | |||||
| <div class="bg-01"></div> | |||||
| <div class="bg-02"></div> | |||||
| <div class="bg-03"></div> | |||||
| <div class="bg-04"></div> | |||||
| </div> | |||||
| <div class="title-c"> | |||||
| <div class="title-main">科技创新2030</div> | |||||
| <div class="title-second">新一代人工智能重大项目成果展示</div> | |||||
| </div> | |||||
| <div class="menu"> | |||||
| <TopMenu :menu="menu"></TopMenu> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import TopMenu from './TopMenu.vue'; | |||||
| export default { | |||||
| name: "TopHeader", | |||||
| props: { | |||||
| menu: { type: String, default: '' }, | |||||
| }, | |||||
| components: { | |||||
| TopMenu | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| list: [], | |||||
| }; | |||||
| }, | |||||
| methods: {}, | |||||
| mounted() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .bg-container { | |||||
| height: 200px; | |||||
| width: 100%; | |||||
| 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.9059999999999998%2C%20-0.8390000000000001%2C%200.016184413580246918%2C%200.9059999999999998%2C%20-0.183%2C%200.683)%22%3E%3Cstop%20stop-color%3D%22%233bb6fe%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%232250cf%22%20stop-opacity%3D%221%22%20offset%3D%220.61%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%237c23b4%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"); | |||||
| overflow: hidden; | |||||
| position: relative; | |||||
| .bg { | |||||
| position: absolute; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| left: 67%; | |||||
| top: 23%; | |||||
| .bg-01 { | |||||
| position: absolute; | |||||
| top: -296.85px; | |||||
| left: -100.21px; | |||||
| width: 11.52px; | |||||
| height: 430.36px; | |||||
| transform: rotate(46deg); | |||||
| border-radius: 45px; | |||||
| display: flex; | |||||
| 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(2.6570000000000005%2C%20-1.366%2C%201906.3802884596842%2C%202.6570000000000005%2C%20-0.714%2C%201.313)%22%3E%3Cstop%20stop-color%3D%22%2315cf16%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ed6bc9%22%20stop-opacity%3D%220%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"); | |||||
| } | |||||
| .bg-02 { | |||||
| position: absolute; | |||||
| top: -130.68px; | |||||
| left: -213.01px; | |||||
| width: 3.64px; | |||||
| height: 430.36px; | |||||
| transform: rotate(46deg); | |||||
| border-radius: 45px; | |||||
| 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(2.6570000000000005%2C%20-1.366%2C%2019094.65120710059%2C%202.6570000000000005%2C%20-0.714%2C%201.313)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ed6bc9%22%20stop-opacity%3D%220%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"); | |||||
| } | |||||
| .bg-03 { | |||||
| position: absolute; | |||||
| top: -178.07px; | |||||
| left: -50.045px; | |||||
| width: 111.15px; | |||||
| height: 306.28px; | |||||
| transform: rotate(46deg); | |||||
| border-radius: 63px; | |||||
| 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(2.6570000000000005%2C%20-1.366%2C%2010.372156049382713%2C%202.6570000000000005%2C%20-0.714%2C%201.313)%22%3E%3Cstop%20stop-color%3D%22%23f8da77%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ed6bc9%22%20stop-opacity%3D%220%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"); | |||||
| } | |||||
| .bg-04 { | |||||
| position: absolute; | |||||
| top: -218.72px; | |||||
| left: 239.81px; | |||||
| width: 20.38px; | |||||
| height: 306.28px; | |||||
| transform: rotate(46deg); | |||||
| border-radius: 45px; | |||||
| 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(2.6570000000000005%2C%20-1.366%2C%20308.5173674049777%2C%202.6570000000000005%2C%20-0.714%2C%201.313)%22%3E%3Cstop%20stop-color%3D%22%2377f8f8%22%20stop-opacity%3D%220.6%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ed6bc9%22%20stop-opacity%3D%220%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"); | |||||
| } | |||||
| } | |||||
| .title-c { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| z-index: 1; | |||||
| .title-main { | |||||
| text-align: center; | |||||
| color: rgba(255, 230, 21, 1); | |||||
| font-weight: 700; | |||||
| text-shadow: rgb(34 7 94) 0px 2px 6px; | |||||
| line-height: 39px; | |||||
| font-size: 28px; | |||||
| margin: 26px 0 4px; | |||||
| } | |||||
| .title-second { | |||||
| text-align: center; | |||||
| font-weight: 700; | |||||
| text-shadow: rgb(34 7 94) 0px 2px 6px; | |||||
| color: rgb(255, 255, 255); | |||||
| font-size: 36px; | |||||
| font-weight: 500; | |||||
| line-height: 50px; | |||||
| } | |||||
| } | |||||
| .menu { | |||||
| position: absolute; | |||||
| bottom: 0; | |||||
| height: 40px; | |||||
| width: 100%; | |||||
| z-index: 2; | |||||
| padding: 0 140px; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,120 @@ | |||||
| <template> | |||||
| <div class="container ui" style="padding:0 75px;"> | |||||
| <div class="menu"> | |||||
| <div class="menu-l"> | |||||
| <div class="menu-item" v-for="(item, index) in listL" :key="index" | |||||
| :class="focusMenu == item.key ? 'focused' : ''" @click="changeMenu(item)"> | |||||
| {{ item.title }} | |||||
| </div> | |||||
| </div> | |||||
| <div class="menu-r"> | |||||
| <div class="menu-item" v-for="(item, index) in listR" :key="index" | |||||
| :class="focusMenu == item.key ? 'focused' : ''" @click="changeMenu(item)"> | |||||
| {{ item.title }} | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import { getIsTechAdmin } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| name: "TopMenu", | |||||
| props: { | |||||
| menu: { type: String, default: '' }, | |||||
| }, | |||||
| components: {}, | |||||
| data() { | |||||
| return { | |||||
| focusMenu: '', | |||||
| listL: [{ | |||||
| key: 'tech_view', | |||||
| title: '按科技项目查看', | |||||
| url: '/tech/tech_view', | |||||
| }, { | |||||
| key: 'repo_view', | |||||
| title: '按项目成果查看', | |||||
| url: '/tech/repo_view', | |||||
| }], | |||||
| listR: [{ | |||||
| key: 'my_view', | |||||
| title: '我申请的项目', | |||||
| url: '/tech/my_view', | |||||
| },], | |||||
| isTechAdmin: false, | |||||
| }; | |||||
| }, | |||||
| methods: { | |||||
| changeMenu(item, index) { | |||||
| this.focusMenu = item.key; | |||||
| window.location.href = item.url; | |||||
| } | |||||
| }, | |||||
| beforeMount() { | |||||
| this.focusMenu = this.menu; | |||||
| getIsTechAdmin().then(res => { | |||||
| res = res.data; | |||||
| if (res.data && res.data.is_admin) { | |||||
| this.isTechAdmin = true; | |||||
| this.listR.push({ | |||||
| key: 'admin_view', | |||||
| title: '管理展示项目', | |||||
| url: '/tech/admin_view', | |||||
| }); | |||||
| } | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| }); | |||||
| }, | |||||
| mounted() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .menu { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| .menu-l { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| } | |||||
| .menu-r { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: flex-end; | |||||
| } | |||||
| .menu-item { | |||||
| display: flex; | |||||
| border-color: rgb(187, 187, 187); | |||||
| border-width: 1px 1px 0px 0px; | |||||
| border-style: solid; | |||||
| font-size: 14px; | |||||
| line-height: 20px; | |||||
| color: rgba(255, 255, 255, 0.7); | |||||
| align-items: center; | |||||
| text-align: center; | |||||
| justify-content: center; | |||||
| background: rgba(104, 50, 165, 0.2); | |||||
| height: 40px; | |||||
| width: 178px; | |||||
| cursor: pointer; | |||||
| &:first-child { | |||||
| border-left-width: 1px; | |||||
| } | |||||
| &.focused { | |||||
| color: rgb(255, 255, 255); | |||||
| background: rgba(249, 249, 249, 0.2); | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -0,0 +1,641 @@ | |||||
| <template> | |||||
| <div> | |||||
| <TopHeader :menu="''"></TopHeader> | |||||
| <div class="ui container"> | |||||
| <div class="title">您申请的项目将在2030科技项目页面展示</div> | |||||
| <div class="form-c" v-show="!selectTechPrj"> | |||||
| <div class="form-wrap"> | |||||
| <div class="form-header">申请展示项目</div> | |||||
| <div class="form-content"> | |||||
| <div class="form-row"> | |||||
| <div class="row-label required">申请项目存放于</div> | |||||
| <div class="row-content"> | |||||
| <el-radio v-model="form.type" label="openi" @input="changeType()">启智社区</el-radio> | |||||
| <el-radio v-model="form.type" label="no-openi" @input="changeType()">非启智社区</el-radio> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" :class="form.url_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required">现项目地址</div> | |||||
| <div class="row-content"> | |||||
| <el-input size="medium" v-model="form.url" @input="changeUrl" class="can-err" | |||||
| placeholder='请输入现有项目的 HTTP(s) 或 Git " clone" URL,如:https://openi.pcl.ac.cn/OpenI/aiforge'></el-input> | |||||
| </div> | |||||
| </div> | |||||
| <div v-show="form.type == 'no-openi'"> | |||||
| <div class="form-row" :class="form.alias_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required baseline">启智项目名称</div> | |||||
| <div class="row-content"> | |||||
| <div> | |||||
| <el-input size="medium" v-model="form.repo_alias" @input="changeAlias" class="can-err"></el-input> | |||||
| </div> | |||||
| <div class="tips"> | |||||
| 请输入中文、字母、数字和-_ .,最多100个字符。 | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" :class="form.name_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required baseline">启智项目路径</div> | |||||
| <div class="row-content"> | |||||
| <div class="reop-url-c"> | |||||
| <el-select size="medium" class="owner-sel" v-model="form.uid" @change="changeOwner"> | |||||
| <div slot="prefix" class="owner-item" style="height:100%;padding-left:10px"> | |||||
| <img class="owner-img" :src="ownerSelect.RelAvatarLink"> | |||||
| </div> | |||||
| <el-option v-for="item in ownerList" :key="item.value" :value="item.value" :label="item.label"> | |||||
| <div class="owner-item"> | |||||
| <img class="owner-img" :src="item.RelAvatarLink"> | |||||
| <span class="owner-name">{{(item.FullName || item.Name)}}</span> | |||||
| </div> | |||||
| </el-option> | |||||
| </el-select> | |||||
| <span style="margin: 0 8px;font-size:22px;"> / </span> | |||||
| <el-input size="medium" v-model="form.repo_name" @input="changeRepoName" class="can-err"></el-input> | |||||
| </div> | |||||
| <div class="tips"> | |||||
| 启智项目地址:<span class="openi-repo-url">{{ form.repo_url }}</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" :class="form.topic_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required">关键词</div> | |||||
| <div class="row-content"> | |||||
| <el-select style="width:100%" size="medium" v-model="form.topics" multiple filterable remote | |||||
| allow-create default-first-option placeholder="请选择标签" :remote-method="searchTopics" | |||||
| :loading="form.topicLoading" class="can-err"> | |||||
| <el-option v-for="item in topicsList" :key="item.value" :label="item.label" :value="item.value"> | |||||
| </el-option> | |||||
| </el-select> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" :class="form.descr_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required baseline">项目简介</div> | |||||
| <div class="row-content"> | |||||
| <el-input size="medium" type="textarea" :rows="4" placeholder="请输入项目简介(长度不超过255)" :maxlength="255" | |||||
| show-word-limit v-model="form.description" class="can-err"></el-input> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" :class="form.tech_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required">科技项目</div> | |||||
| <div class="row-content"> | |||||
| <el-input placeholder="请选择科技项目" size="medium" v-model="form.tech_show" :readonly="true" class="can-err"> | |||||
| <el-button size="medium" slot="append" class="btn-select" @click="goSelectTechPrj">选择科技项目</el-button> | |||||
| <el-button size="medium" slot="append" class="btn-clear" @click="clearTechPrj">清除</el-button> | |||||
| </el-input> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-row" :class="form.institution_err ? 'form-row-err' : ''"> | |||||
| <div class="row-label required">成果贡献单位</div> | |||||
| <div class="row-content"> | |||||
| <el-select style="width:100%" size="medium" v-model="form.institution" multiple class="can-err"> | |||||
| <el-option v-for="item in institutionList" :key="item.key" :label="item.value" | |||||
| :value="item.value"></el-option> | |||||
| </el-select> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-btn-group"> | |||||
| <el-button size="medium" type="primary" :loading="submitLoading" class="btn confirm-btn" | |||||
| @click="submit">提交申请</el-button> | |||||
| <el-button size="medium" class="btn" @click="cancel">{{ $t('cancel') }}</el-button> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-select-tech-prj" v-show="selectTechPrj"> | |||||
| <div class="form-wrap"> | |||||
| <div class="form-header"> | |||||
| <span>选择科技项目</span> | |||||
| <span> | |||||
| <el-button class="form-btn-go-back" @click="goBack">返回</el-button> | |||||
| </span> | |||||
| </div> | |||||
| <div class="form-content"> | |||||
| <div class="form-row"> | |||||
| <div class="row-label">请输入科技项目</div> | |||||
| <div class="row-content"> | |||||
| <el-input placeholder="请输入搜索内容" size="medium" v-model="form.tech_search_keyword" | |||||
| @keyup.enter.native="searchTechList" class="input-with-select"> | |||||
| <el-select v-model="form.tech_search_sel" style="width:142px" size="medium" slot="prepend"> | |||||
| <el-option label="项目立项编号" value="0"></el-option> | |||||
| <el-option label="参与单位" value="1"></el-option> | |||||
| <el-option label="项目名称" value="2"></el-option> | |||||
| </el-select> | |||||
| <el-button size="medium" slot="append" icon="el-icon-search" @click="searchTechList"></el-button> | |||||
| </el-input> | |||||
| </div> | |||||
| </div> | |||||
| <div class="form-table"> | |||||
| <div style="margin: 0 20px 30px 20px"> | |||||
| <el-table ref="tableRef" border :data="tableData" style="width:100%" v-loading="loading" stripe> | |||||
| <el-table-column prop="no" label="项目立项编号" align="center" header-align="center" | |||||
| width="120"></el-table-column> | |||||
| <el-table-column prop="name" label="科技项目名称" align="center" header-align="center" | |||||
| width="200"></el-table-column> | |||||
| <el-table-column prop="institution" label="项目承担单位" align="center" header-align="center" | |||||
| width="200"></el-table-column> | |||||
| <el-table-column prop="all_institution" label="所有参与单位" align="center" | |||||
| header-align="center"></el-table-column> | |||||
| <el-table-column width="100" :label="$t('operation')" align="center" header-align="center"> | |||||
| <template slot-scope="scope"> | |||||
| <el-button type="primary" @click="selectedTechPrj(scope.row)">选择</el-button> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import TopHeader from '../components/TopHeader.vue'; | |||||
| import { getTechs, setOpenIApply, setNoOpenIApply, getCreateRepoUser, getCheckRepoName, getTopics } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| form: { | |||||
| type: 'openi', | |||||
| url: '', | |||||
| url_err: false, | |||||
| repo_alias: '', | |||||
| alias_err: false, | |||||
| uid: '', | |||||
| repo_name: '', | |||||
| name_err: false, | |||||
| repo_url: '', | |||||
| topics: [], | |||||
| topic_err: false, | |||||
| topicLoading: false, | |||||
| description: '', | |||||
| descr_err: false, | |||||
| tech_search_sel: '0', | |||||
| tech_search_keyword: '', | |||||
| tech_obj: null, | |||||
| tech_show: '', | |||||
| tech_err: false, | |||||
| institution: [], | |||||
| institution_err: false, | |||||
| }, | |||||
| selectTechPrj: false, | |||||
| loading: false, | |||||
| tableData: [], | |||||
| topicsList: [], | |||||
| institutionList: [], | |||||
| ownerList: [], | |||||
| ownerSelect: {}, | |||||
| submitLoading: false, | |||||
| }; | |||||
| }, | |||||
| components: { | |||||
| TopHeader, | |||||
| }, | |||||
| methods: { | |||||
| resetData() { | |||||
| this.form.url = ''; | |||||
| this.form.url_err = false; | |||||
| this.form.repo_alias = ''; | |||||
| this.form.alias_err = false; | |||||
| this.form.uid = this.ownerList.length ? this.ownerList[0].value : ''; | |||||
| this.form.repo_name = ''; | |||||
| this.form.repo_url = ''; | |||||
| this.form.name_err = false; | |||||
| this.form.topics = []; | |||||
| this.form.topic_err = false; | |||||
| this.form.topicLoading = false; | |||||
| this.form.description = ''; | |||||
| this.form.descr_err = false; | |||||
| this.form.tech_search_sel = '0'; | |||||
| this.form.tech_search_keyword = ''; | |||||
| this.form.tech_obj = null; | |||||
| this.form.tech_show = ''; | |||||
| this.form.tech_err = false; | |||||
| this.form.institution = []; | |||||
| this.form.institution_err = false; | |||||
| this.tableData = []; | |||||
| this.topicsList = []; | |||||
| this.institutionList = []; | |||||
| this.submitLoading = false; | |||||
| }, | |||||
| changeType() { | |||||
| this.resetData(); | |||||
| }, | |||||
| checkUrl() { | |||||
| this.form.url_err = !this.form.url; | |||||
| return !this.form.url_err; | |||||
| }, | |||||
| checkRepoAlias() { | |||||
| const reg = /^[\u4E00-\u9FA5A-Za-z0-9_.-]{1,100}$/; | |||||
| const res = reg.test(this.form.repo_alias); | |||||
| this.form.alias_err = !res; | |||||
| return res; | |||||
| }, | |||||
| checkRepoName() { | |||||
| const reg = /^[A-Za-z0-9_.-]{1,100}$/; | |||||
| const res = reg.test(this.form.repo_name); | |||||
| this.form.name_err = !res; | |||||
| return res; | |||||
| }, | |||||
| checkTopic() { | |||||
| this.form.topic_err = this.form.topics.length == 0; | |||||
| return !this.form.topic_err; | |||||
| }, | |||||
| checkDescr() { | |||||
| this.form.descr_err = !this.form.description; | |||||
| return !this.form.descr_err; | |||||
| }, | |||||
| checkTech() { | |||||
| this.form.tech_err = !this.form.tech_obj; | |||||
| return !this.form.tech_err; | |||||
| }, | |||||
| checkinstitution() { | |||||
| this.form.institution_err = this.form.institution.length == 0; | |||||
| return !this.form.institution_err; | |||||
| }, | |||||
| changeUrl() { | |||||
| if (this.form.type == 'openi') return; | |||||
| const owner = this.ownerSelect.Name; | |||||
| const urlAdd = location.href.split("/")[0] + "//" + location.href.split("/")[2]; | |||||
| const repoValue = this.form.url.match(/^(.*\/)?((.+?)(\.git)?)$/)[3]; | |||||
| this.form.repo_alias = repoValue; | |||||
| this.checkRepoAlias(); | |||||
| getCheckRepoName({ owner: owner, q: repoValue }).then(res => { | |||||
| const repo_name = res.data.name; | |||||
| this.form.repo_name = repo_name; | |||||
| this.form.repo_url = `${urlAdd}/${owner}/${repo_name}.git`; | |||||
| this.checkRepoName(); | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.form.repo_name = ''; | |||||
| this.form.repo_url = ''; | |||||
| }); | |||||
| }, | |||||
| changeAlias() { | |||||
| const owner = this.ownerSelect.Name; | |||||
| const aliasValue = this.form.repo_alias; | |||||
| const urlAdd = location.href.split("/")[0] + "//" + location.href.split("/")[2]; | |||||
| if (aliasValue && this.checkRepoAlias()) { | |||||
| getCheckRepoName({ owner: owner, q: aliasValue }).then(res => { | |||||
| const repo_name = res.data.name; | |||||
| this.form.repo_name = repo_name; | |||||
| this.form.repo_url = `${urlAdd}/${owner}/${repo_name}.git`; | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.form.repo_name = ''; | |||||
| this.form.repo_url = ''; | |||||
| }); | |||||
| } else { | |||||
| this.form.repo_name = ''; | |||||
| this.form.repo_url = ''; | |||||
| } | |||||
| }, | |||||
| changeOwner(value) { | |||||
| this.ownerSelect = this.ownerList.filter(item => item.value == value)[0]; | |||||
| this.form.repo_name && this.changeRepoName(); | |||||
| }, | |||||
| changeRepoName() { | |||||
| const owner = this.ownerSelect.Name; | |||||
| const repo_name = this.form.repo_name; | |||||
| const urlAdd = location.href.split("/")[0] + "//" + location.href.split("/")[2]; | |||||
| if (this.checkRepoName()) { | |||||
| this.form.repo_url = `${urlAdd}/${owner}/${repo_name}.git`; | |||||
| } else { | |||||
| this.form.repo_url = ''; | |||||
| } | |||||
| }, | |||||
| searchTopics(query) { | |||||
| if (query !== '') { | |||||
| this.form.topicLoading = true; | |||||
| getTopics({ q: query }).then(res => { | |||||
| this.form.topicLoading = false; | |||||
| const topics = res.data.topics || []; | |||||
| this.topicsList = topics.map(item => { | |||||
| return { | |||||
| value: item.topic_name, | |||||
| label: item.topic_name, | |||||
| } | |||||
| }); | |||||
| }).catch(err => { | |||||
| this.topicsList = []; | |||||
| }); | |||||
| } else { | |||||
| this.topicsList = []; | |||||
| } | |||||
| }, | |||||
| goSelectTechPrj() { | |||||
| this.form.tech_search_sel = '0'; | |||||
| this.form.tech_search_keyword = ''; | |||||
| this.tableData = []; | |||||
| this.selectTechPrj = true; | |||||
| this.searchTechList(); | |||||
| }, | |||||
| clearTechPrj() { | |||||
| this.form.institution = []; | |||||
| this.institutionList = []; | |||||
| this.form.tech_show = ''; | |||||
| this.form.tech_obj = null; | |||||
| this.form.tech_err = false; | |||||
| }, | |||||
| goBack() { | |||||
| this.selectTechPrj = false; | |||||
| }, | |||||
| searchTechList() { | |||||
| this.loading = true; | |||||
| getTechs({ | |||||
| no: this.form.tech_search_sel == '0' ? this.form.tech_search_keyword : '', | |||||
| institution: this.form.tech_search_sel == '1' ? this.form.tech_search_keyword : '', | |||||
| name: this.form.tech_search_sel == '2' ? this.form.tech_search_keyword : '', | |||||
| }).then(res => { | |||||
| this.loading = false; | |||||
| res = res.data; | |||||
| this.tableData = res.data || []; | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| this.loading = false; | |||||
| this.tableData = []; | |||||
| }); | |||||
| }, | |||||
| selectedTechPrj(item) { | |||||
| this.form.institution = []; | |||||
| this.institutionList = item.all_institution.split(',').map((item, index) => { | |||||
| return { | |||||
| key: item, | |||||
| value: item, | |||||
| } | |||||
| }); | |||||
| this.form.tech_show = `【${item.no}】 ${item.name}`; | |||||
| this.form.tech_obj = item; | |||||
| this.checkTech(); | |||||
| this.goBack(); | |||||
| }, | |||||
| submit() { | |||||
| const subData = {}; | |||||
| let setApi = null; | |||||
| if (this.form.type == 'openi') { | |||||
| setApi = setOpenIApply; | |||||
| const r1 = this.checkUrl(); | |||||
| const r2 = this.checkTech(); | |||||
| const r3 = this.checkinstitution(); | |||||
| if (r1 && r2 && r3) { | |||||
| subData.url = this.form.url; | |||||
| subData.no = this.form.tech_obj.no; | |||||
| subData.institution = this.form.institution.join(','); | |||||
| } else { | |||||
| return; | |||||
| } | |||||
| } else { | |||||
| setApi = setNoOpenIApply; | |||||
| const r1 = this.checkUrl(); | |||||
| const r2 = this.checkTech(); | |||||
| const r3 = this.checkinstitution(); | |||||
| const r4 = this.checkRepoAlias(); | |||||
| const r5 = this.checkRepoName(); | |||||
| const r6 = this.checkTopic(); | |||||
| const r7 = this.checkDescr(); | |||||
| if (r1 && r2 && r3 && r4 && r5 && r6 && r7) { | |||||
| subData.url = this.form.url; | |||||
| subData.uid = this.form.uid; | |||||
| subData.alias = this.form.repo_alias; | |||||
| subData.repo_name = this.form.repo_name; | |||||
| subData.topics = [...this.form.topics]; | |||||
| subData.description = this.form.description.trim(); | |||||
| subData.no = this.form.tech_obj.no; | |||||
| subData.institution = this.form.institution.join(','); | |||||
| } else { | |||||
| return; | |||||
| } | |||||
| } | |||||
| // console.log(subData); | |||||
| // return; | |||||
| this.submitLoading = true; | |||||
| setApi(subData).then(res => { | |||||
| if (res.data && res.data.code == 0) { | |||||
| this.$message({ | |||||
| type: 'success', | |||||
| message: this.$t('submittedSuccessfully'), | |||||
| }); | |||||
| setTimeout(() => { | |||||
| window.location.href = '/tech/tech_view'; | |||||
| }, 1000); | |||||
| } else { | |||||
| this.submitLoading = false; | |||||
| this.$message({ | |||||
| type: 'info', | |||||
| message: res.data.msg | |||||
| }); | |||||
| } | |||||
| }).catch(err => { | |||||
| this.submitLoading = false; | |||||
| this.$message({ | |||||
| type: 'error', | |||||
| message: this.$t('submittedFailed'), | |||||
| }); | |||||
| }); | |||||
| }, | |||||
| cancel() { | |||||
| window.history.back(); | |||||
| } | |||||
| }, | |||||
| beforeMount() { | |||||
| getCreateRepoUser().then(res => { | |||||
| const data = res.data.Data || []; | |||||
| this.ownerList = data.map(item => { | |||||
| return { | |||||
| value: item.ID, | |||||
| label: item.FullName || item.Name, | |||||
| ...item, | |||||
| } | |||||
| }); | |||||
| this.ownerSelect = this.ownerList.length ? this.ownerList[0] : {}; | |||||
| this.form.uid = this.ownerList.length ? this.ownerList[0].value : ''; | |||||
| }).catch(err => { | |||||
| console.log(err); | |||||
| }); | |||||
| }, | |||||
| mounted() { }, | |||||
| beforeDestroy() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .title { | |||||
| text-align: center; | |||||
| margin: 30px 0; | |||||
| font-size: 14px; | |||||
| color: rgb(16, 16, 16); | |||||
| } | |||||
| .form-c, | |||||
| .form-select-tech-prj { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| .form-wrap { | |||||
| width: 1000px; | |||||
| background: rgb(255, 255, 255); | |||||
| border-color: rgb(212, 212, 213); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| border-radius: 5px; | |||||
| box-sizing: border-box; | |||||
| .form-header { | |||||
| height: 45px; | |||||
| background: rgb(240, 240, 240); | |||||
| border-bottom: 1px solid rgb(212, 212, 213); | |||||
| color: rgb(16, 16, 16); | |||||
| padding-left: 16px; | |||||
| padding-right: 16px; | |||||
| font-size: 16px; | |||||
| font-weight: 700; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: space-between; | |||||
| } | |||||
| .form-content { | |||||
| margin-top: 40px; | |||||
| .form-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 80%; | |||||
| margin: 20px auto; | |||||
| padding-right: 50px; | |||||
| .row-label { | |||||
| flex: 2; | |||||
| text-align: right; | |||||
| margin-right: 24px; | |||||
| position: relative; | |||||
| &.required { | |||||
| &::after { | |||||
| position: absolute; | |||||
| top: -2px; | |||||
| right: -10px; | |||||
| content: '*'; | |||||
| color: #db2828; | |||||
| } | |||||
| } | |||||
| &.baseline { | |||||
| align-self: baseline; | |||||
| margin-top: 11px; | |||||
| } | |||||
| } | |||||
| .row-content { | |||||
| flex: 9; | |||||
| .tips { | |||||
| margin-top: 4px; | |||||
| font-size: 14px; | |||||
| color: rgb(136, 136, 136); | |||||
| } | |||||
| } | |||||
| .reop-url-c { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| .openi-repo-url { | |||||
| color: rgba(16, 16, 16, 1); | |||||
| } | |||||
| &.form-row-err { | |||||
| .row-label { | |||||
| color: #9f3a38; | |||||
| } | |||||
| /deep/ .can-err .el-input__inner, | |||||
| /deep/ .can-err .el-textarea__inner { | |||||
| color: #9f3a38; | |||||
| background: #fff6f6; | |||||
| border-color: #e0b4b4; | |||||
| } | |||||
| } | |||||
| } | |||||
| .form-table { | |||||
| margin-top: 30px; | |||||
| margin-bottom: 30px; | |||||
| /deep/ .el-table__header { | |||||
| th { | |||||
| background: rgb(249, 249, 249); | |||||
| font-size: 12px; | |||||
| color: rgb(136, 136, 136); | |||||
| font-weight: normal; | |||||
| } | |||||
| } | |||||
| /deep/ .el-table__body { | |||||
| td { | |||||
| font-size: 12px; | |||||
| } | |||||
| } | |||||
| /deep/ .el-radio__label { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| .form-btn-group { | |||||
| display: flex; | |||||
| justify-content: center; | |||||
| align-items: center; | |||||
| margin: 30px 0 40px; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .btn { | |||||
| color: rgb(2, 0, 4); | |||||
| background-color: rgb(194, 199, 204); | |||||
| border-color: rgb(194, 199, 204); | |||||
| &.confirm-btn { | |||||
| color: #fff; | |||||
| background-color: rgb(56, 158, 13); | |||||
| border-color: rgb(56, 158, 13); | |||||
| margin-right: 20px; | |||||
| } | |||||
| } | |||||
| .btn-select { | |||||
| background-color: rgb(50, 145, 248) !important; | |||||
| color: white !important; | |||||
| border-radius: 0 !important; | |||||
| height: 35px; | |||||
| } | |||||
| .owner-sel { | |||||
| /deep/ .el-input__inner { | |||||
| padding-left: 48px; | |||||
| } | |||||
| } | |||||
| .owner-item { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .owner-img { | |||||
| width: 24px; | |||||
| height: 24px; | |||||
| margin-right: 10px; | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -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'); | |||||
| @@ -0,0 +1,164 @@ | |||||
| <template> | |||||
| <div> | |||||
| <TopHeader :menu="'my_view'"></TopHeader> | |||||
| <div class="ui container"> | |||||
| <div class="table-container"> | |||||
| <div class="table-title">2030科技项目</div> | |||||
| <div class="table-wrap"> | |||||
| <el-table ref="tableRef" border :data="tableData" style="width:100%;" v-loading="loading" stripe row-key="id"> | |||||
| <el-table-column label="序号" type="index" align="center" header-align="center" width="50" | |||||
| fixed></el-table-column> | |||||
| <el-table-column label="启智项目名称" align="center" header-align="center" fixed min-width="140"> | |||||
| <template slot-scope="scope"> | |||||
| <span v-if="scope.row.status == 5"> | |||||
| {{ `${scope.row.url.split('/')[3]}/${scope.row.url.split('/')[4]}` }} | |||||
| </span> | |||||
| <a v-else target="_blank" :href="`/${scope.row.repo_owner_name}/${scope.row.repo_name}`"> | |||||
| {{ `${scope.row.repo_owner_name}/${scope.row.repo_name}` }} | |||||
| </a> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="name" label="科技项目名称" align="center" header-align="center" fixed | |||||
| min-width="200"></el-table-column> | |||||
| <el-table-column prop="status" label="展示状态" align="center" header-align="center" fixed> | |||||
| <template slot-scope="scope"> | |||||
| <span style="color:rgb(255, 37, 37)" | |||||
| :style="scope.row.status == 1 ? { color: 'rgb(56, 158, 13)' } : ''">{{ | |||||
| statusMap[scope.row.status] }}</span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| <el-table-column prop="no" label="项目立项编号" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="institution" label="项目承担单位" align="center" header-align="center" | |||||
| min-width="120"></el-table-column> | |||||
| <el-table-column prop="all_institution" label="所有参与单位" align="center" header-align="center" | |||||
| min-width="280"></el-table-column> | |||||
| <el-table-column prop="contribution_institution" label="成果贡献单位" align="center" header-align="center" | |||||
| min-width="280"></el-table-column> | |||||
| <el-table-column prop="contribution_institution" label="申请时间" align="center" header-align="center" | |||||
| min-width="120"> | |||||
| <template slot-scope="scope"> | |||||
| <span>{{ dateFormat(scope.row.created_unix) }}</span> | |||||
| </template> | |||||
| </el-table-column> | |||||
| </el-table> | |||||
| </div> | |||||
| <div class="center"> | |||||
| <el-pagination ref="paginationRef" background @current-change="currentChange" @size-change="sizeChange" | |||||
| :current-page.sync="page" :page-sizes="pageSizes" :page-size.sync="pageSize" | |||||
| layout="total, sizes, prev, pager, next, jumper" :total="total"> | |||||
| </el-pagination> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import dayjs from 'dayjs'; | |||||
| import TopHeader from '../components/TopHeader.vue'; | |||||
| import { getTechMyList } from '~/apis/modules/tech'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| loading: false, | |||||
| tableData: [], | |||||
| page: 1, | |||||
| pageSizes: [15, 30, 50], | |||||
| pageSize: 15, | |||||
| total: 0, | |||||
| statusMap: { | |||||
| '1': '已展示', | |||||
| '2': '未展示', | |||||
| '3': '项目迁移中', | |||||
| '4': '项目迁移失败', | |||||
| '5': '项目不存在', | |||||
| }, | |||||
| }; | |||||
| }, | |||||
| components: { TopHeader }, | |||||
| methods: { | |||||
| getData() { | |||||
| this.loading = true; | |||||
| getTechMyList({ | |||||
| page: this.page, | |||||
| pageSize: this.pageSize, | |||||
| }).then(res => { | |||||
| this.loading = false; | |||||
| const { total, data } = res.data; | |||||
| this.tableData = data.map((item, index) => { | |||||
| return { ...item, } | |||||
| }); | |||||
| this.total = total; | |||||
| }).catch(err => { | |||||
| this.loading = false; | |||||
| console.log(err); | |||||
| }); | |||||
| }, | |||||
| currentChange(page) { | |||||
| this.page = page; | |||||
| this.getData(); | |||||
| }, | |||||
| sizeChange(pageSize) { | |||||
| this.pageSize = pageSize; | |||||
| this.getData(); | |||||
| }, | |||||
| dateFormat(unix) { | |||||
| return dayjs(unix * 1000).format('YYYY-MM-DD HH:mm'); | |||||
| } | |||||
| }, | |||||
| beforeMount() { | |||||
| this.getData(); | |||||
| }, | |||||
| mounted() { }, | |||||
| beforeDestroy() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .table-container { | |||||
| margin-top: 30px; | |||||
| .table-title { | |||||
| height: 43px; | |||||
| font-size: 16px; | |||||
| font-weight: 700; | |||||
| padding: 15px; | |||||
| color: rgb(16, 16, 16); | |||||
| border-color: rgb(212, 212, 213); | |||||
| border-width: 1px; | |||||
| border-style: solid; | |||||
| border-radius: 5px 5px 0px 0px; | |||||
| background: rgb(240, 240, 240); | |||||
| display: flex; | |||||
| align-items: center; | |||||
| } | |||||
| .table-wrap { | |||||
| overflow-x: auto; | |||||
| margin-bottom: 20px; | |||||
| /deep/ .el-table__header { | |||||
| th { | |||||
| background: rgb(249, 249, 249); | |||||
| font-size: 12px; | |||||
| color: rgb(136, 136, 136); | |||||
| font-weight: normal; | |||||
| } | |||||
| } | |||||
| /deep/ .el-table__body { | |||||
| td { | |||||
| font-size: 12px; | |||||
| } | |||||
| } | |||||
| /deep/ .el-radio__label { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @@ -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'); | |||||
| @@ -0,0 +1,100 @@ | |||||
| <template> | |||||
| <div> | |||||
| <TopHeader :menu="'repo_view'"></TopHeader> | |||||
| <div class="ui container"> | |||||
| <SearchBar :type="1" :condition="condition" @changeCondition="changeCondition"></SearchBar> | |||||
| <div class="conent-c"> | |||||
| <div class="filter-c"> | |||||
| <Filters :type="1" :condition="condition" @changeCondition="changeCondition"></Filters> | |||||
| </div> | |||||
| <div class="result-c"> | |||||
| <PrjResultsList ref="resultListRef" :condition="condition" @changeCondition="changeCondition"> | |||||
| </PrjResultsList> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import TopHeader from '../components/TopHeader.vue'; | |||||
| import SearchBar from '../components/SearchBar.vue'; | |||||
| import Filters from '../components/Filters.vue'; | |||||
| import PrjResultsList from '../components/PrjResultsList.vue'; | |||||
| import { getUrlSearchParams } from '~/utils'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| condition: { | |||||
| q: '', // 搜索框 | |||||
| topic: '', // 关键词 | |||||
| project_name: '', // 项目类型 | |||||
| institution_name: '', // 成果贡献单位 | |||||
| page: '', | |||||
| pageSize: '', | |||||
| sort: '', | |||||
| }, | |||||
| pageSizes: [15, 30, 50], | |||||
| }; | |||||
| }, | |||||
| components: { | |||||
| TopHeader, | |||||
| SearchBar, | |||||
| Filters, | |||||
| PrjResultsList | |||||
| }, | |||||
| methods: { | |||||
| changeCondition(params) { | |||||
| this.condition = { | |||||
| ...this.condition, | |||||
| ...params, | |||||
| }; | |||||
| if (!params.changePage) { | |||||
| this.condition.page = 1; | |||||
| } | |||||
| window.location.href = `/tech/repo_view?` + `q=${encodeURIComponent(this.condition.q.trim())}` + | |||||
| `&topic=${encodeURIComponent(this.condition.topic)}` + | |||||
| `&project_name=${encodeURIComponent(this.condition.project_name)}` + | |||||
| `&institution_name=${encodeURIComponent(this.condition.institution_name)}` + | |||||
| `&page=${encodeURIComponent(this.condition.page)}` + | |||||
| `&pageSize=${encodeURIComponent(this.condition.pageSize)}` + | |||||
| `&sort=${encodeURIComponent(this.condition.sort)}`; | |||||
| }, | |||||
| }, | |||||
| beforeMount() { | |||||
| const urlParams = getUrlSearchParams(); | |||||
| this.condition.q = urlParams.q || ''; | |||||
| this.condition.topic = urlParams.topic || ''; | |||||
| this.condition.project_name = urlParams.project_name || ''; | |||||
| this.condition.institution_name = urlParams.institution_name || ''; | |||||
| this.condition.sort = urlParams.sort || ''; | |||||
| this.condition.page = Number(urlParams.page) || 1; | |||||
| this.condition.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15; | |||||
| this.$nextTick(() => { | |||||
| this.$refs.resultListRef.search(); | |||||
| }); | |||||
| }, | |||||
| mounted() { }, | |||||
| beforeDestroy() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .conent-c { | |||||
| display: flex; | |||||
| } | |||||
| .filter-c { | |||||
| flex: 1; | |||||
| padding-left: 12px; | |||||
| } | |||||
| .result-c { | |||||
| margin-left: 10px; | |||||
| flex: 3; | |||||
| width: 0; | |||||
| } | |||||
| </style> | |||||
| @@ -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'); | |||||
| @@ -0,0 +1,100 @@ | |||||
| <template> | |||||
| <div> | |||||
| <TopHeader :menu="'tech_view'"></TopHeader> | |||||
| <div class="ui container"> | |||||
| <SearchBar :type="0" :condition="condition" @changeCondition="changeCondition"></SearchBar> | |||||
| <div class="conent-c"> | |||||
| <div class="filter-c"> | |||||
| <Filters :type="0" :condition="condition" @changeCondition="changeCondition"></Filters> | |||||
| </div> | |||||
| <div class="result-c"> | |||||
| <SciAndTechPrjList ref="resultListRef" :condition="condition" @changeCondition="changeCondition"> | |||||
| </SciAndTechPrjList> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </template> | |||||
| <script> | |||||
| import TopHeader from '../components/TopHeader.vue'; | |||||
| import SearchBar from '../components/SearchBar.vue'; | |||||
| import Filters from '../components/Filters.vue'; | |||||
| import SciAndTechPrjList from '../components/SciAndTechPrjList.vue'; | |||||
| import { getUrlSearchParams } from '~/utils'; | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| condition: { | |||||
| q: '', // 搜索框 | |||||
| type_name: '', // 项目类型 | |||||
| institution_name: '', // 项目参与单位 | |||||
| execute_year: '', // 执行周期包含年份 | |||||
| apply_year: '', | |||||
| page: '', | |||||
| pageSize: '', | |||||
| sort: '', | |||||
| }, | |||||
| pageSizes: [15, 30, 50], | |||||
| }; | |||||
| }, | |||||
| components: { | |||||
| TopHeader, | |||||
| SearchBar, | |||||
| Filters, | |||||
| SciAndTechPrjList | |||||
| }, | |||||
| methods: { | |||||
| changeCondition(params) { | |||||
| this.condition = { | |||||
| ...this.condition, | |||||
| ...params, | |||||
| }; | |||||
| if (!params.changePage) { | |||||
| this.condition.page = 1; | |||||
| } | |||||
| window.location.href = `/tech/tech_view?` + `q=${encodeURIComponent(this.condition.q.trim())}` + | |||||
| `&type_name=${encodeURIComponent(this.condition.type_name)}` + | |||||
| `&institution_name=${encodeURIComponent(this.condition.institution_name)}` + | |||||
| `&execute_year=${encodeURIComponent(this.condition.execute_year)}` + | |||||
| `&apply_year=${encodeURIComponent(this.condition.apply_year)}` + | |||||
| `&page=${encodeURIComponent(this.condition.page)}` + | |||||
| `&pageSize=${encodeURIComponent(this.condition.pageSize)}` + | |||||
| `&sort=${encodeURIComponent(this.condition.sort)}`; | |||||
| }, | |||||
| }, | |||||
| beforeMount() { | |||||
| const urlParams = getUrlSearchParams(); | |||||
| this.condition.q = urlParams.q || ''; | |||||
| this.condition.type_name = urlParams.type_name || ''; | |||||
| this.condition.institution_name = urlParams.institution_name || ''; | |||||
| this.condition.execute_year = urlParams.execute_year || ''; | |||||
| this.condition.apply_year = urlParams.apply_year || ''; | |||||
| this.condition.sort = urlParams.sort || ''; | |||||
| this.condition.page = Number(urlParams.page) || 1; | |||||
| this.condition.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15; | |||||
| this.$nextTick(() => { | |||||
| this.$refs.resultListRef.search(); | |||||
| }); | |||||
| }, | |||||
| mounted() { }, | |||||
| beforeDestroy() { }, | |||||
| }; | |||||
| </script> | |||||
| <style scoped lang="less"> | |||||
| .conent-c { | |||||
| display: flex; | |||||
| } | |||||
| .filter-c { | |||||
| flex: 1; | |||||
| padding-left: 12px; | |||||
| } | |||||
| .result-c { | |||||
| margin-left: 10px; | |||||
| flex: 3; | |||||
| } | |||||
| </style> | |||||
| @@ -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'); | |||||