搜索结果列表页面开发
This commit is contained in:
83
src/components/RepoItem/README.md
Normal file
83
src/components/RepoItem/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# RepoItem组件
|
||||
|
||||
### 说明
|
||||
- 通用的项目卡片,demo如下
|
||||
|
||||
``` js
|
||||
// template
|
||||
<g-repo-item
|
||||
:iconHandleList="iconHandleList"
|
||||
:hideRight="true"
|
||||
:id="item.id"
|
||||
:imgSrc="item.imgSrc"
|
||||
:title="item.title"
|
||||
:tag="item.tag"
|
||||
:desc="item.desc"
|
||||
:isStar="item.isStar"
|
||||
@handleStar="handleStar"
|
||||
/>
|
||||
// script
|
||||
const iconHandleList = toRef([
|
||||
{ icon: '', value: 'C', iconColor: 'red',type: 'language', label: '语言', to: '' },
|
||||
{ icon: 'gt-star', value: 2100, label: '关注', to: '' },
|
||||
{ icon: 'gt-fork', value: 975, label: 'Fork', to: '' },
|
||||
{ icon: 'gt-date', value: '2023-07-20', type:'updateTime', label: '', to: '' }
|
||||
]);
|
||||
const item = ref({
|
||||
id: '1',
|
||||
imgSrc: '',
|
||||
title: 'kernel_liteos_a',
|
||||
desc: 'LiteOS kernel for embedded devices with rich resources',
|
||||
isStar: false,
|
||||
tag: 'Public',
|
||||
web_url:''
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
|----------------|-------------------------|-------------------------------|-----------|-------|
|
||||
| title | 标题 | string | — | — |
|
||||
| tag? | 标签 | string | — | — |
|
||||
| desc? | 描述 | string | — | — |
|
||||
| imgSrc? | 头像src | string | — | — |
|
||||
| id | 唯一标识id | string | — | — |
|
||||
| iconHandleList? | icon操作列表 | array | 见下表 | [] |
|
||||
| isStar? | 是否satr | boolean | — | false |
|
||||
| hideStar? | 是否显示身份标签 | boolean | — | true |
|
||||
| hideTag? | 隐藏tag | boolean | — | false |
|
||||
| hideDesc? | 隐藏描述 | boolean | — | false |
|
||||
| hideRight? | 隐藏右侧图表区域 | boolean | — | true |
|
||||
| web_url? | 点击标题跳转url | string | — | — |
|
||||
| keywords? | 关键词高亮 | string | — | — |
|
||||
| hasShadow? | 是否为卡片投影样式 | boolean | — | false(默认为底部边框) |
|
||||
|
||||
#### Props(iconHandleList)
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||
| ---------- | --------- | ---------------- | ------------------------------------------------------------ | ------ |
|
||||
| icon? | 图标name | string | — | — |
|
||||
| value | 显示名称 | string \| number | — | — |
|
||||
| label? | hover文案 | string | — | — |
|
||||
| to? | 跳转路径 | string | — | — |
|
||||
| type? | 字段类型 | string | 语言type需传language,则显示特定样式;时间需传:createdTime \| updateTime,则会转格式如:1天以前 | string |
|
||||
| iconColor? | icon颜色 | string | — | — |
|
||||
|
||||
|
||||
|
||||
### Emits
|
||||
|
||||
注意:以下事件都是组件内部对应的按钮点击触发,如果用插槽自定义对应区域的内容的话,事件自行监听即可
|
||||
|
||||
| 方法 | 说明 | 返回值 |
|
||||
|-----------|--------|-----------------------|
|
||||
| handleStar | 点击star | data: {id:'',isStar:true} |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|--------------------|-------------------------------|
|
||||
| right | 组件右侧图表区域 |
|
||||
|
||||
33
src/components/RepoItem/datahandle.ts
Normal file
33
src/components/RepoItem/datahandle.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { RepoItemResData, TagItem } from './types';
|
||||
import { useTimeFormat } from '@/utils/hooks/useTimeFormat';
|
||||
import { repoTypeOption } from '@/constant';
|
||||
const { formatTimeFromNow } = useTimeFormat();
|
||||
export const dataHandler = (data: RepoItemResData[], mapper?: Function) => {
|
||||
return data.map((item, index) => { // 项目详情处理
|
||||
const iconHandleList = []; const lang: any[] = []; const langColor: any[] = [];
|
||||
item.main_repository_language?.forEach((v, i) => v && [lang, langColor][i % 2].push(v));
|
||||
if (lang.length) iconHandleList.push({ icon: 'icon-dot-status', value: lang[0], type: 'language', iconColor: langColor[0], label: '', to: '' });
|
||||
iconHandleList.push({ icon: 'gt-star', value: item.star_count, label: '', to: '', iconColor: '#707A87' });
|
||||
// 传了fork数量即显示,fork数量可能为0
|
||||
if (item.forks_count || item.forks_count === 0) iconHandleList.push({ icon: 'gt-fork', value: item.forks_count, label: '', to: '', iconColor: '#707A87' });
|
||||
if (item?.license?.key) iconHandleList.push({ icon: 'gt-license', value: item.license?.key, label: '', to: '', iconColor: '#707A87' });
|
||||
iconHandleList.push({ icon: 'gt-date', value: formatTimeFromNow(item.last_activity_at), label: '', to: '', iconColor: '#707A87' });
|
||||
const tagList: TagItem[] = [];
|
||||
if (item.visibility && item.visibility !== 'public') tagList.push({ name: repoTypeOption.find(e => e.value === item.visibility)?.name || '', color: 'rgb(148, 163, 184)' });
|
||||
// if (item.is_recommend) tagList.push({ name: '官方镜像', color: '#fff', borderColor: 'var(--color-GN500)', bgColor: 'var(--color-GN500)' });
|
||||
const def = mapper ? mapper({ ...item, iconHandleList, tagList }) : {};
|
||||
return {
|
||||
...item,
|
||||
imgSrc: '',
|
||||
title: item.name || '',
|
||||
desc: item.description || '暂无简介',
|
||||
isStar: item.starred || false,
|
||||
tagList,
|
||||
topicNames: item.topic_names,
|
||||
to: `/${item.path_with_namespace}`,
|
||||
namePath: item.name_with_namespace?.split('/'),
|
||||
iconHandleList,
|
||||
...def
|
||||
};
|
||||
});
|
||||
};
|
||||
363
src/components/RepoItem/index.vue
Normal file
363
src/components/RepoItem/index.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div class="g-repo-item px-4 py-5" :class="{
|
||||
'is-mobile': mobile,
|
||||
'g-repo-item-simple': hideDesc,
|
||||
'g-repo-item-img': imgSrc,
|
||||
'g-repo-item-shadow': hasShadow
|
||||
}" shadow="never">
|
||||
<div class="flex gap-2">
|
||||
<div class="ali-left flex flex-grow w-[0px]">
|
||||
<GAvatar v-if="imgSrc" class="g-repo-item-avatar" :width="19" :height="19" :is_round="false" :src="imgSrc" :name="title"/>
|
||||
<div class="w-full">
|
||||
<div>
|
||||
<div class="font-[500] text-lg repo-title">
|
||||
<slot name="name" hightlights="highlightWords">
|
||||
<a class="flex-1" :href="web_url" target="_blank" v-html="highlightWords(keywords, title)" v-add-title-at-ellipsis></a>
|
||||
</slot>
|
||||
<custom-tag v-for="(tag,index) in tagList" :key="index" :color="tag.color||'#9fa7b3'" :border-color="tag.borderColor||'#9fa7b3'" :bg-color="tag.bgColor||'#fff'"
|
||||
class="repo-tag shrink-0 cursor-pointer max-w-[120px] truncate ml-2" style="line-height: 18px;vertical-align: -3.5px;" @click="handleTag(tag,index)">{{ tag.name }}</custom-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hideDesc" class="g-repo-item-desc mt-2 leading-[20px] line-clamp-2"
|
||||
:class="{ 'g-repo-item-desc-height': hasShadow }" v-html="highlightWords(keywords, desc)" v-add-title-at-ellipsis></div>
|
||||
<!-- <div class="g-repo-item-topic" v-if="topicNames?.length > 0">
|
||||
<div class="topic-item" v-for="item in topicNames" :key="item">
|
||||
<d-tooltip :content="item" :disabled="!item">
|
||||
<span @click.stop="handleGoTopic(item)">{{ item }}</span>
|
||||
</d-tooltip>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="mt-2 flex items-center">
|
||||
<template v-for="(item, index) in iconHandleList" :key="index">
|
||||
<div class="g-repo-item-edit" :class="{ 'g-repo-item-edit-link': item.to }" @click.stop="handleGo(item.to || '')">
|
||||
<d-tooltip :content="item.label" :disabled="!item.label">
|
||||
<div class="g-repo-item-flex">
|
||||
<template v-if="item.type === 'language'">
|
||||
<span class="g-repo-item-lan items-center">
|
||||
<div class="g-repo-item-lan-line" :style="`background-color:${item.iconColor}`"></div>
|
||||
<div>{{ item.value || '' }}</div>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon :name="item.icon" :color="item.iconColor" v-if="item.value !== null" class="g-repo-item-edit-icon">
|
||||
</Icon>
|
||||
<span v-if="item.type === 'createdTime'"><Time :time="item.value" />创建</span>
|
||||
<span v-else-if="item.type === 'updateTime'"> <Time :time="item.value" />更新 </span>
|
||||
<template v-else>
|
||||
<Number :number="item.value" v-if="typeof item.value === 'number'" />
|
||||
<span v-else>{{ item.value || '-' }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</d-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ali-right" v-if="!hideRight">
|
||||
<StarBtn :active="isStar"
|
||||
:interface="starInterface(<string>id)"
|
||||
@starChange="handleStar"></StarBtn>
|
||||
</div>
|
||||
</div>
|
||||
<slot name="bottom"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import CustomTag from '@/components/CustomTag/index.vue';
|
||||
import { highlightWords } from '@/utils';
|
||||
import type{ RepoItemResData, iconHandleItem, TagItem } from './types';
|
||||
import StarBtn from '@/components/StarBtn/StarBtn.vue';
|
||||
import { starInterface } from '@/components/StarBtn/starService';
|
||||
|
||||
const router = useRouter();
|
||||
const props = defineProps({
|
||||
hideStar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideDesc: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
hideRight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
tagList: {
|
||||
type: Array<TagItem>,
|
||||
default: () => []
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
topicNames: {
|
||||
type: Array<String>,
|
||||
default: ''
|
||||
},
|
||||
isStar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
imgSrc: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
iconHandleList: {
|
||||
type: Array<iconHandleItem>,
|
||||
default: () => []
|
||||
},
|
||||
web_url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
keywords: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
hasShadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mobile: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
|
||||
const em = defineEmits(['handleStar', 'tagClick']);
|
||||
|
||||
const handleStar = (starActive:boolean) => {
|
||||
em('handleStar', {isStar: starActive });
|
||||
};
|
||||
const handleTag = (tag:TagItem, index:Number) => {
|
||||
em('tagClick', { tag, index });
|
||||
};
|
||||
const handleGo = (link: string) => {
|
||||
if (!link) return;
|
||||
router.push(link);
|
||||
};
|
||||
const handleGoTopic = (item: string) => {
|
||||
if (!item) return;
|
||||
router.push('/topic/' + item);
|
||||
};
|
||||
</script>
|
||||
<script lang="ts">
|
||||
import { dataHandler } from './datahandle';
|
||||
export { dataHandler };
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.g-repo-item {
|
||||
background-color: $devui-base-bg;
|
||||
width: 100%;
|
||||
border-bottom: solid 1px var(--color-border-light);
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: inherit;
|
||||
/* 使悬停颜色与原色相同 */
|
||||
}
|
||||
:deep(.g-custom-tag){
|
||||
margin-right: 0px;
|
||||
}
|
||||
&-shadow {
|
||||
padding: 16px 20px 20px 20px;
|
||||
box-shadow: $devui-shadow-length-base $devui-light-shadow;
|
||||
border-radius: $devui-border-radius;
|
||||
}
|
||||
|
||||
&-img {
|
||||
:deep(.devui-card__actions) {
|
||||
margin-left: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
&-simple {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
:deep(.devui-card__actions) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-lan {
|
||||
display: flex;
|
||||
|
||||
&-line {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
margin-top: 1px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
float: auto;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
margin-right: 22px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&-attention {
|
||||
border-radius: var(--border-radius, 4px);
|
||||
border: 1px solid var(--color-border-light);
|
||||
padding: 4px 8px;
|
||||
display: flex;
|
||||
vertical-align: middle;
|
||||
flex-shrink: 0;
|
||||
|
||||
:deep(.button-content) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
|
||||
.icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(i) {
|
||||
margin-right: 0px;
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&-desc {
|
||||
color: var(--color-CG600);
|
||||
font-size: 14px;
|
||||
text-overflow: ellipsis;
|
||||
height: auto;
|
||||
word-break: break-all;
|
||||
max-width: calc(100% - 100px);
|
||||
|
||||
&-height {
|
||||
height: 42px;
|
||||
max-width: calc(100% - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
&-topic {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 5px 0;
|
||||
.topic-item {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
}
|
||||
span {
|
||||
cursor: pointer;
|
||||
background-color: var(--color-CG500);
|
||||
color: #fff;
|
||||
border-radius: 11px;
|
||||
padding: 3px 10px;
|
||||
height: 22px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
max-width: 108px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
&-edit {
|
||||
margin-right: 24px;
|
||||
color: var(--color-light);
|
||||
|
||||
&-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
color: var(--color-CG600);
|
||||
}
|
||||
|
||||
&-right {
|
||||
width: 100%;
|
||||
|
||||
&-img {
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.g-repo-item {
|
||||
&.is-mobile {
|
||||
padding: 12px 20px;
|
||||
|
||||
.g-repo-item-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.g-repo-item-attention {
|
||||
height: 24px !important;
|
||||
line-height: 24px !important;
|
||||
}
|
||||
|
||||
.g-repo-item-desc {
|
||||
font-size: 14px;
|
||||
color: var(--color-CG600);
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-span {
|
||||
line-height: 1;
|
||||
color: var(--color-CG600);
|
||||
}
|
||||
</style>
|
||||
|
||||
42
src/components/RepoItem/types.ts
Normal file
42
src/components/RepoItem/types.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export type RepoItemResData = {
|
||||
id: number;
|
||||
name: string;
|
||||
namespace: string;
|
||||
path: string;
|
||||
develop_mode: string;
|
||||
tagList: TagItem[];
|
||||
visibility: string;
|
||||
star_count: number;
|
||||
forks_count: number;
|
||||
open_issues_count: number;
|
||||
open_merge_requests_count: number;
|
||||
open_change_requests_count: number;
|
||||
starred: boolean;
|
||||
name_with_namespace: string;
|
||||
web_url: string;
|
||||
topic_names: string[];
|
||||
last_activity_at: Date;
|
||||
main_repository_language: null[];
|
||||
forked_from_project: null;
|
||||
permissions: null;
|
||||
archived: boolean;
|
||||
member_count: number;
|
||||
description: null;
|
||||
repository_size: number;
|
||||
[propName: string]: any;
|
||||
}
|
||||
export interface iconHandleItem {
|
||||
icon: string;
|
||||
value: string | number;
|
||||
label: string;
|
||||
to?: string;
|
||||
iconColor?: string;
|
||||
type?: string;
|
||||
}
|
||||
export interface TagItem {
|
||||
name:string;
|
||||
color?:string;
|
||||
bgColor?:string;
|
||||
borderColor?:string;
|
||||
[propName:string]:any;
|
||||
}
|
||||
Reference in New Issue
Block a user