搜索结果列表页面开发

This commit is contained in:
付民康
2025-03-12 18:41:20 +08:00
commit 7cebe8fc00
739 changed files with 88149 additions and 0 deletions

View 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 | 组件右侧图表区域 |

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

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

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