搜索结果列表页面开发

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,254 @@
<template>
<Card v-if="titles?.length > 0">
<div class="actlis">
<div v-for="(item, index) in carouselItems" :key="index" class="actlis-item cursor-pointer hover:text-link"
:class="index != carouselItems.length - 1 ? ' mr-20' : ''" @click="openHref(item.link)">
<div class="actlis-title">
<div>
<GAvatar :src="icons[index]" :width="19" :height="19" />
<span class="actlis-span">{{ titles[index] }}</span>
</div>
<span v-if="titles[index] !== '热门直播'" class="actlis-time"><Time :time="item?.created_at" /></span>
<span v-else class="actlis-time"><Time :time="item?.activity_time" /></span>
</div>
<div class="actlis-imgdiv">
<img class="actlis-img" :src="item?.picture_url" v-if="titles[index] !== '热门文章'" />
<div class="actlis-titdiv actlis-column text-CG800 text-base" v-else>
<div :id="'titref' + index" class="actlis-2line font-bold">{{ item?.title }}</div>
<div class="actlis-3line mt-[16px]" :style="{ '-webkit-line-clamp': lineNums[index] }">{{ item?.desc }}</div>
</div>
</div>
</div>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</div>
</Card>
</template>
<script setup lang="ts">
defineOptions({ name: 'activity-list' });
import { ref, useSlots, type StyleValue, computed, nextTick, watch } from 'vue';
const IconActive = new URL('@/assets/imgs/org/icon_active.png', import.meta.url).href;
const IconAi = new URL('@/assets/imgs/org/icon_ai.png', import.meta.url).href;
const IconVideo = new URL('@/assets/imgs/org/icon_video.png', import.meta.url).href;
interface ActiData {
activity_time?: string;
link?: string;
picture_url?: string;
title?: string;
[propName: string]: any;
}
interface IData {
operationContent?: {
activity: ActiData[],
featured: ActiData[],
calendar: ActiData[],
live: ActiData[]
},
visitableReport?: boolean; // 举报 默认可见
}
const lineHeight = (id: string) => {
const el = document.querySelector('#' + id);
if (el && el.clientHeight > 24) {
return 4;
}
return 5;
};
const props = withDefaults(defineProps<IData>(), {
operationContent: () => ({
activity: [],
featured: [],
calendar: []
}),
visitableReport: true
});
// 链接跳转
const openHref = (url?: string) => {
window.open(url, '_blank', '');
};
const slots = useSlots();
const emit = defineEmits<{(e: 'report', /* orgname : string */): void }>();
// const titles = ['最新活动', '热门文章', '热门直播'];
// const icons = [IconActive, IconAi, IconVideo];
const icons = computed(() => {
const iconAry = [];
props.operationContent.featured && props.operationContent.featured.forEach((item, index) => {
if (index < 3) {
iconAry.push(IconAi);
}
});
if (props.operationContent.activity && props.operationContent.activity.length > 0) {
iconAry[0] = IconActive;
}
if (props.operationContent.featured && props.operationContent.featured.length > 0) {
iconAry[1] = IconAi;
}
if (props.operationContent.live && props.operationContent.live.length > 0) {
iconAry[2] = IconVideo;
}
return iconAry.filter(item => item);
});
const titles = computed(() => {
const titAry = [];
props.operationContent.featured && props.operationContent.featured.forEach((item, index) => {
if (index < 3) {
titAry.push('热门文章');
}
});
if (props.operationContent.activity && props.operationContent.activity.length > 0) {
titAry[0] = '最新活动';
}
if (props.operationContent.featured && props.operationContent.featured.length > 0) {
titAry[1] = '热门文章';
}
if (props.operationContent.live && props.operationContent.live.length > 0) {
titAry[2] = '热门直播';
}
return titAry.filter(item => item);
});
const carouselItems = computed(() => {
const dataList = [];
let featIndex: number = 0;
props.operationContent.featured && props.operationContent.featured.forEach((item, index) => {
if (index < 3) {
featIndex = index;
dataList.push(item);
}
});
if (props.operationContent.activity && props.operationContent.activity.length > 0) {
featIndex = 0;
dataList[0] = props.operationContent.activity[0];
}
if (props.operationContent.featured && props.operationContent.featured.length > 1) {
dataList[1] = props.operationContent.featured[featIndex === 0 ? 0 : 1];
}
if (props.operationContent.live && props.operationContent.live.length > 0) {
dataList[2] = props.operationContent.live[0];
}
return dataList.filter(item => item);
});
const lineNums = ref<number[]>([]);
watch(() => carouselItems.value, (val) => {
if (val && val.length > 0) {
nextTick(() => {
val.forEach((item: any, index: number) => {
lineNums.value.push(lineHeight('titref' + index));
});
});
}
}, { deep: true });
</script>
<style lang="scss" scoped>
$imgHeight: 181px;
.actlis {
width: 100%;
// height: 200px;
// border: 1px solid #E6E7E8;
// padding: 22px 0 20px 20px;
display: flex;
// background: #FFFFFF;
.actlis-item {
flex: 1;
width: 0;
height: 100%;
display: flex;
flex-direction: column;
.actlis-title {
display: flex;
align-items: center;
justify-content: space-between;
line-height: 20px;
:deep(.devui-avatar) {
vertical-align: middle;
}
.icon-comment {
font-size: 16px;
}
.actlis-span {
margin-left: 8px;
color: var(--color-CG800);
font-size: var(--text-sm);
font-weight: 500;
}
.actlis-time {
color: var(--color-G700);
font-size: var(--text-sm);
font-weight: 400;
}
}
.actlis-imgdiv {
width: 100%;
flex: 1;
height: 0;
margin-top: 18px;
display: flex;
align-items: center;
justify-content: center;
background: #F8F9FB;
}
.actlis-img {
width: 100%;
height: $imgHeight;
object-fit: cover;
// height: 120px;
}
.actlis-titdiv {
width: 100%;
height: $imgHeight;
padding: 8px;
font-weight: 500;
// display: flex;
// align-items: center;
}
}
}
.actlis-noline {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.actlis-2line {
width: 100%;
word-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2; //(行数)
-webkit-box-orient: vertical;
white-space: normal;
word-break: break-all;
}
.actlis-3line {
width: 100%;
word-wrap: break-word;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 4; //(行数)
-webkit-box-orient: vertical;
white-space: normal;
word-break: break-all;
}
.actlis-column {
flex-direction: column;
// justify-content: center;
}
</style>

View File

@@ -0,0 +1,175 @@
<template>
<div>
<Panel class="selite overflow-hidden" :blank="false">
<template #header>
<div class="selite-lefthead">
<Icon name="gt-comment-c" size="16px" class="mr-20" />
<span class="selite-dspan">社区动态</span>
</div>
</template>
<template #headerRight>
<span class="cursor-pointer" @click="naviTo">
<GIcon name="gt-more-operate" />
</span>
</template>
<DataPanel class="org-repos-table overflow-hidden" :loading="loadingStatus.dynamicLoading" :empty="!comList?.length" :card="false"
skeleton>
<template v-for="(item, index) in comList" :key="item.id">
<div class="selite-content px-[20px] py-[16px]">
<div>
<span class="text-B600 text-base font-medium leading-[24px]">#{{ item.category.category_name
}}</span>
<span class="ml-[8px] text-G900 text-base font-medium leading-[24px]">
<GLink class="info-content__title ellipsis"
:to="{ name: `orgDiscussionDetail`, params: { serialNumber: item.serial_number } }">
{{ item.title }}
</GLink>
</span>
</div>
<div class="mt-[8px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.created_by_user_name }}</span>
<span class="ml-[16px] text-CG600 text-sm font-normal leading-[20px]">创建于<Time
:time="item.created_date" /></span>
<GIcon class="ml-[16px]" name="gt-comment" />
<span class="ml-[4px] text-CG600 text-sm font-normal leading-[20px]">{{ item.comment_total }}</span>
<GIcon v-if="item.isAnswered === 1" color="#0EB07B" class="ml-[16px]" name="gt-success" />
<span v-if="item.isAnswered === 1"
class="ml-[4px] text-GN500 text-sm font-normal leading-[20px]">评论已采纳</span>
</div>
</div>
<div class="line"></div>
</template>
</DataPanel>
</Panel>
<d-pagination class=" px-[20px] py-[20px] flex justify-center" :page-size-options="[10, 20, 50]" auto-hide
:total="discussPager.total" v-model:pageSize="discussPager.pageSize" v-model:pageIndex="discussPager.pageIndex"
:can-view-total="true" :can-change-page-size="true" @page-index-change="getDiscussData()"
@page-size-change="sizeChangeFun" :max-items="5" />
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'community-dynamics' });
import { ref, reactive, watch, computed } from 'vue';
import { useRouter } from 'vue-router';
import Panel from '@/components/Panel/index.vue';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
const { namespace, discussData, discussPager, loadingStatus, getDiscussData } = getOrgInfo();
getDiscussData();
const comList = computed(() => {
return discussData.value;
});
const router = useRouter();
// 更多跳转
const naviTo = () => {
router.push({
name: 'orgDiscussion',
params: { namespace: namespace.value }
});
};
// 每页条数变化
const sizeChangeFun = (size: number) => {
discussPager.pageIndex = 1;
getDiscussData();
};
// 监听组织路径变化
// 监听路由后面需要优化成路由监听
let lastNameSpace = namespace.value;
watch(() => namespace.value, (val, oldVal) => {
if (val !== lastNameSpace) {
onUpdate();
}
lastNameSpace = val;
}, {
deep: true
});
// 项目设置后更新
const onUpdate = () => {
getDiscussData();
};
</script>
<style lang="scss" scoped>
.selite {
.selite-lefthead {
line-height: 20px;
display: flex;
align-items: center;
.selite-icon {
margin-right: 21px;
}
.selite-dspan {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
}
}
.selite-content {
.is-last {
:deep(.g-repo-item) {
border-bottom: none;
}
}
:deep(.g-repo-item) {
border-top: none;
border-left: none;
border-right: none;
border-radius: 0;
box-shadow: none;
}
}
.selite-flex {
display: flex;
align-items: center;
justify-content: center;
}
.line-1 {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.text-B600 {
color: #2562C4;
}
.text-GN500 {
color: #0EB07B;
}
.des-div {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
.line {
width: 100%;
height: 1px;
background: #F0F1F2;
}
.info-content__title {
cursor: pointer;
&:hover {
color: var(--color-font);
}
}
}
</style>

View File

@@ -0,0 +1,272 @@
<template>
<Panel class="selite overflow-hidden" :blank="false" v-if="!loadingStatus.activityLoading && activityData?.length">
<template #header>
<div class="selite-lefthead">
<Icon name="gt-calendar-c" size="16px" class="mr-20" />
<span class="selite-dspan">最新活动</span>
</div>
</template>
<template #headerRight>
<span class="cursor-pointer" @click="naviTo">
<GIcon name="gt-more-operate" />
</span>
</template>
<div class="selite-content flex">
<DataPanel :loading="loadingStatus.activityLoading" :empty="!activityData?.length" animation skeleton
class="px-[10px] py-[20px] w-full" :card="false">
<swiper :slidesPerView="'auto'" :spaceBetween="0" :cssMode="true" :navigation="true" :mousewheel="true"
:keyboard="true" :modules="modules" class="mySwiper">
<swiper-slide v-for="item in activityData" :key="item.mediaAid">
<div class="px-[10px] w-[370px] overflow-hidden">
<div class="relative">
<div class="cursor-pointer relative flex items-center justify-center" @click="openHref(item.mediaAid)">
<img :src="item.ext?.coverImg || orgDevimg" class="h-[198px] w-full object-cover" />
<span v-if="!item.ext?.coverImg" class="absolute px-[16px]">{{ item.title || '最新活动' }}</span>
</div>
<div v-if="item.recommend"
class="absolute w-[40px] h-[18px] top-[4px] right-[4px] bg-[#F5AB0B] text-[#FFFFFF] text-xs font-normal leading-[18px] text-center rounded-[2px]">
精选</div>
<!-- <div v-if="item.type === 0 && new Date() < new Date(item.endAt)"
class="absolute w-[40px] h-[18px] top-[4px] right-[4px] bg1 text-[#FFFFFF] text-xs font-normal leading-[18px] text-center rounded-[2px]">
直播</div> -->
<div v-if="item.type === 0 && new Date() <= new Date(item.endAt) && new Date() >= new Date(item.startAt)"
class="flex items-center justify-center absolute w-[56px] h-[18px] top-[4px] left-[4px] bg2 text-[#FFFFFF] text-xs font-normal leading-[18px] text-center rounded-[2px]">
<div class="zb-div"></div>
<span class="ml-[4px]">直播</span>
</div>
<div v-if="false || item.type === 0 && new Date() > new Date(item.endAt)"
class="absolute top-[4px] right-[4px] flex items-center justify-center">
<div
class="bg-[rgba(0,0,0,0.5)] px-[4px] py-[1px] text-[#FFFFFF] text-xs font-normal leading-[16px] rounded-[2px]">
08:35</div>
<div
class="ml-[4px] bg-[rgba(0,0,0,0.5)] px-[4px] py-[1px] text-[#FFFFFF] text-xs font-normal leading-[16px] rounded-[2px]">
23.5万次播放</div>
</div>
</div>
<div class="mt-[16px] text-G900 text-sm font-medium leading-[20px] line-1 w-full">{{ item.title || '-'
}}
</div>
<div class="mt-[4px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.user.nickname }}</span>
<span class="ml-[8px] text-CG600 text-sm font-normal leading-[20px]">{{ item.startAt }}</span>
</div>
</div>
</swiper-slide>
<!-- <swiper-slide v-if="repoHandpickLists2.length > 0">
<div class="flex px-[10px] w-full overflow-hidden">
<template v-for="(item, index) in repoHandpickLists2" :key="item.id">
<div class="px-[10px] py-[20px] w-[350px] shrink-0">
<div class="cursor-pointer relative flex items-center justify-center" @click="openHref(item.contentUrl)">
<img :src="item.ext?.coverImg || orgDevimg" class="h-[198px]" />
<span v-if="!item.ext?.coverImg" class="absolute ">{{ item.title || '最新活动' }}</span>
</div>
<div class="mt-[16px] text-G900 text-sm font-medium leading-[20px] line-1 w-full">{{ item.title || '-' }}
</div>
<div class="mt-[4px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.user.nickname }}</span>
<span class="ml-[8px] text-CG600 text-sm font-normal leading-[20px]">{{ item.startAt }}</span>
</div>
</div>
</template>
</div>
</swiper-slide>
<swiper-slide v-if="repoHandpickLists3.length > 0">
<div class="flex px-[10px] w-full overflow-hidden">
<template v-for="(item, index) in repoHandpickLists3" :key="item.id">
<div class="px-[10px] py-[20px] w-[350px] shrink-0">
<div class="cursor-pointer relative flex items-center justify-center" @click="openHref(item.contentUrl)">
<img :src="item.ext?.coverImg || orgDevimg" class="h-[198px]" />
<span v-if="!item.ext?.coverImg" class="absolute ">{{ item.title || '最新活动' }}</span>
</div>
<div class="mt-[16px] text-G900 text-sm font-medium leading-[20px] line-1 w-full">{{ item.title || '-' }}
</div>
<div class="mt-[4px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.user.nickname }}</span>
<span class="ml-[8px] text-CG600 text-sm font-normal leading-[20px]">{{ item.startAt }}</span>
</div>
</div>
</template>
</div>
</swiper-slide> -->
</swiper>
</DataPanel>
</div>
</Panel>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</template>
<script setup lang="ts">
defineOptions({ name: 'dev-new-activity-list' });
import { ref, reactive, useSlots, type StyleValue, watch, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import Panel from '@/components/Panel/index.vue';
import RepoItem from '@/components/RepoItem/index.vue';
import RepoSelectModal from '@/views/User/components/repo-select-modal.vue';
import { useUserInfo } from '@/views/User/hooks/useUserInfo';
import { emitEvent } from '@/utils/eventBus';
import { storeToRefs } from 'pinia';
import { getRepos, starRepo, unstarRepo } from '@/api/repo';
import { dataHandler, useRepoList, type RepoItemData } from '@/views/User/hooks/useRepoList';
import { getOrgHandpickProjectList, getOrgProjectList } from '@/api/org/index';
import { reqCatch } from '@/utils/catch';
import * as types from '@/api/org/types';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
import { orgInfoStore } from '@/stores/Org';
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
// import required modules
import { Navigation, Pagination, Mousewheel, Keyboard } from 'swiper/modules';
import orgDevimg from '@/assets/imgs/org/org-devimg.png';
const modules = [Navigation, Pagination, Mousewheel, Keyboard];
const baseDevURL = (import.meta as any).env.VITE_DEV_HOST;
// 获取组织path方法
const { namespace, loadingStatus, activityData, getDevActivity } = getOrgInfo();
const { isSelf, userInfo } = useUserInfo();
const route = useRoute();
const { isAdmin, communityInfo } = storeToRefs(orgInfoStore());
// getDevActivity();
// 链接跳转
const openHref = (id?: string) => {
window.open(`/organization/${namespace.value}/${id}.html`, '_blank', '');
};
// 更多跳转
const naviTo = () => {
window.location.assign(`/organization/${namespace.value}/activity`);
};
// 项目设置后更新
const onUpdate = () => {
getDevActivity();
};
// 监听组织路径变化
// 监听路由后面需要优化成路由监听
let lastNameSpace = '';
watch(() => [namespace.value, communityInfo.value], (val, oldVal) => {
const [name, info] = val;
if (name !== lastNameSpace && Object.keys(info).length) {
onUpdate();
lastNameSpace = name;
}
}, {
deep: true,
immediate: true
});
</script>
<style lang="scss" scoped>
.selite {
.selite-lefthead {
line-height: 20px;
display: flex;
align-items: center;
.selite-icon {
margin-right: 21px;
}
.selite-dspan {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
}
}
.selite-content {
.is-last {
:deep(.g-repo-item) {
border-bottom: none;
}
}
:deep(.g-repo-item) {
border-top: none;
border-left: none;
border-right: none;
border-radius: 0;
box-shadow: none;
}
}
:deep(.swiper-slide) {
width: 370px !important;
}
.selite-flex {
display: flex;
align-items: center;
justify-content: center;
}
.line-1 {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.bg1 {
background: linear-gradient(90deg, #FF6633 0%, #FF4D97 100%);
}
.bg2 {
background: linear-gradient(315deg, #5172FF 0%, #7F98FF 100%);
;
}
.zb-div {
width: 12px;
height: 12px;
background: url('@/assets/imgs/org/dev-liveBroadcast.png') 0 0;
animation: play 1s steps(7) infinite;
}
@keyframes play {
100% {
background-position: -84px;
}
}
:deep(.devui-carousel) {
width: 100%;
}
:deep(.swiper-button-prev:after) {
display: none;
}
:deep(.swiper-button-next:after) {
display: none;
}
:deep(.swiper-button-next) {
border-radius: 50%;
background: url('@/assets/imgs/org/dev-right.png');
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(64, 64, 64, 0.25);
width: 40px;
height: 40px;
backdrop-filter: blur(10px);
pointer-events: auto;
}
:deep(.swiper-button-prev) {
border-radius: 50%;
background: url('@/assets/imgs/org/dev-left.png');
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(64, 64, 64, 0.25);
width: 40px;
height: 40px;
backdrop-filter: blur(10px);
pointer-events: auto;
}
}
</style>

View File

@@ -0,0 +1,256 @@
<template>
<Panel class="selite overflow-hidden" :blank="false" v-if="!loadingStatus.articleLoading && articleData?.length">
<template #header>
<div class="selite-lefthead">
<Icon name="gt-file2-c" size="16px" class="mr-20" />
<span class="selite-dspan">技术文章</span>
</div>
</template>
<template #headerRight>
<span class="cursor-pointer" @click="naviTo">
<GIcon name="gt-more-operate" />
</span>
</template>
<div class="selite-content flex">
<DataPanel :loading="loadingStatus.articleLoading" :empty="!articleData?.length" animation skeleton
class="px-[10px] py-[20px] w-full" :card="false">
<swiper :slidesPerView="'auto'" :spaceBetween="0" :cssMode="true" :navigation="true" :mousewheel="true"
:keyboard="true" :modules="modules" class="mySwiper">
<swiper-slide v-for="item in articleData" :key="item.id">
<div class="px-[10px] w-[370px] overflow-hidden">
<div class="cursor-pointer relative flex items-center justify-center" @click="openHref(item.content.id)">
<img :src="item.content.thumb || orgDevimg" class="h-[198px] w-full object-cover" />
<span v-if="!item.content.thumb" class="absolute cover-tag text-center">#{{
coverTag(item.content.externalData.tags, item.content.name) }}</span>
<div v-if="item.content.top > 0"
class="absolute w-[40px] h-[18px] top-[4px] right-[4px] bg-[#F5AB0B] text-[#FFFFFF] text-xs font-normal leading-[18px] text-center rounded-[2px]">
精选</div>
</div>
<div class="mt-[16px] text-G900 text-sm font-medium leading-[20px] line-1 w-full">{{ item.content.name }}
</div>
<div class="mt-[4px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.nickname }}</span>
<span class="ml-[8px] text-CG600 text-sm font-normal leading-[20px]"><Time
:time="item.content.createdTime" /></span>
</div>
</div>
</swiper-slide>
<!-- <swiper-slide v-if="repoHandpickLists2.length > 0">
<div class="flex px-[10px] w-full overflow-hidden">
<template v-for="(item, index) in repoHandpickLists2" :key="item.id">
<div class="px-[10px] py-[20px] w-[350px] shrink-0">
<div class="cursor-pointer relative flex items-center justify-center" @click="openHref(item.content.id)">
<img :src="item.contentUrl || orgDevimg" class="h-[198px]" />
<span v-if="!item.contentUrl" class="absolute ">{{ item.content.externalData.tags[0]?.name || '技术文章' }}</span>
</div>
<div class="mt-[16px] text-G900 text-sm font-medium leading-[20px] line-1 w-full">{{ item.content.desc }}</div>
<div class="mt-[4px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.nickname }}</span>
<span class="ml-[8px] text-CG600 text-sm font-normal leading-[20px]"><Time :time="item.content.createdTime" /></span>
</div>
</div>
</template>
</div>
</swiper-slide>
<swiper-slide v-if="repoHandpickLists3.length > 0">
<div class="flex px-[10px] w-full overflow-hidden">
<template v-for="(item, index) in repoHandpickLists3" :key="item.id">
<div class="px-[10px] py-[20px] w-[350px] shrink-0">
<div class="cursor-pointer relative flex items-center justify-center" @click="openHref(item.content.id)">
<img :src="item.contentUrl || orgDevimg" class="h-[198px]" />
<span v-if="!item.contentUrl" class="absolute ">{{ item.content.externalData.tags[0]?.name || '技术文章' }}</span>
</div>
<div class="mt-[16px] text-G900 text-sm font-medium leading-[20px] line-1 w-full">{{ item.content.desc }}</div>
<div class="mt-[4px]">
<span class="text-CG600 text-sm font-normal leading-[20px]">{{ item.nickname }}</span>
<span class="ml-[8px] text-CG600 text-sm font-normal leading-[20px]"><Time :time="item.content.createdTime" /></span>
</div>
</div>
</template>
</div>
</swiper-slide> -->
</swiper>
</DataPanel>
</div>
</Panel>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</template>
<script setup lang="ts">
defineOptions({ name: 'dev-technical-article' });
import { ref, reactive, useSlots, type StyleValue, watch, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import Panel from '@/components/Panel/index.vue';
import RepoItem from '@/components/RepoItem/index.vue';
import RepoSelectModal from '@/views/User/components/repo-select-modal.vue';
import { useUserInfo } from '@/views/User/hooks/useUserInfo';
import { emitEvent } from '@/utils/eventBus';
import { storeToRefs } from 'pinia';
import { getRepos, starRepo, unstarRepo } from '@/api/repo';
import { dataHandler, useRepoList, type RepoItemData } from '@/views/User/hooks/useRepoList';
import { getOrgHandpickProjectList, getOrgProjectList } from '@/api/org/index';
import { reqCatch } from '@/utils/catch';
import * as types from '@/api/org/types';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
import { orgInfoStore } from '@/stores/Org';
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
// import required modules
import { Navigation, Pagination, Mousewheel, Keyboard } from 'swiper/modules';
import orgDevimg from '@/assets/imgs/org/org-devimg.png';
const modules = [Navigation, Pagination, Mousewheel, Keyboard];
const baseDevURL = (import.meta as any).env.VITE_DEV_HOST;
// 获取组织path方法
const { namespace, loadingStatus, articleData, getDevArticle } = getOrgInfo();
const { isSelf, userInfo } = useUserInfo();
const route = useRoute();
const { isAdmin, communityInfo } = storeToRefs(orgInfoStore());
// const swiperAry = computed(() => {
// const a1 = articleData.value?.content.filter((item: any, index: number) => index < 4) || [];
// const a2 = articleData.value?.content.filter((item: any, index: number) => index > 2 && index < 7) || [];
// const a3 = articleData.value?.content.filter((item: any, index: number) => index > 5 && index < 8) || [];
// return [a1, a2, a3];
// });
// getDevArticle();
// 链接跳转
const openHref = (id?: string) => {
window.open(`/organization/${namespace.value}/${id}.html`, '_blank', '');
};
// 更多跳转
const naviTo = () => {
window.location.assign(`/organization/${namespace.value}/article`);
};
// 项目设置后更新
const onUpdate = () => {
getDevArticle();
};
// 监听组织路径变化
// 监听路由后面需要优化成路由监听
let lastNameSpace: any = '';
watch(() => [namespace.value, communityInfo.value], (val, oldVal) => {
const [name, info] = val;
if (name !== lastNameSpace && Object.keys(info).length) {
onUpdate();
lastNameSpace = name;
}
}, {
deep: true,
immediate: true
});
const coverTag = (tags = [], title = '') => {
const str = tags[0]?.name || title || '技术文章';
return /[\u4e00-\u9fa5]+/.test(str) ? str.slice(0, 6) : str;
};
</script>
<style lang="scss" scoped>
.selite {
.selite-lefthead {
line-height: 20px;
display: flex;
align-items: center;
.selite-icon {
margin-right: 21px;
}
.selite-dspan {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
}
}
.selite-content {
.is-last {
:deep(.g-repo-item) {
border-bottom: none;
}
}
:deep(.g-repo-item) {
border-top: none;
border-left: none;
border-right: none;
border-radius: 0;
box-shadow: none;
}
.cover-tag {
width: fit-content;
height: 70px;
font-size: 50px;
font-weight: bold;
color: #FFFFFF;
line-height: 70px;
white-space: nowrap;
text-overflow: clip;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
}
.selite-flex {
display: flex;
align-items: center;
justify-content: center;
}
:deep(.swiper-slide) {
width: 370px !important;
}
.line-1 {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
:deep(.devui-carousel) {
width: 100%;
}
:deep(.swiper-button-prev:after) {
display: none;
}
:deep(.swiper-button-next:after) {
display: none;
}
:deep(.swiper-button-next) {
border-radius: 50%;
background: url('@/assets/imgs/org/dev-right.png');
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(64, 64, 64, 0.25);
width: 40px;
height: 40px;
backdrop-filter: blur(10px);
pointer-events: auto;
}
:deep(.swiper-button-prev) {
border-radius: 50%;
background: url('@/assets/imgs/org/dev-left.png');
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(64, 64, 64, 0.25);
width: 40px;
height: 40px;
backdrop-filter: blur(10px);
pointer-events: auto;
}
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div>
<!-- 默认标题 可换插槽 #title-->
<div class="evecal-title" v-if="!slots.title">
<div class="evecal-headerleft">
<Icon name="gt-calendar-c" size="16px" />
<span class="evecal-span">活动日历</span>
</div>
<GLink @click="openMoreHref">
<Icon name="gt-all" color="#707A8"></Icon>
</GLink>
</div>
<slot v-else name="title"></slot>
<!-- 内容 -->
<div class="evecal-body">
<div v-for="(item, index) in calendar" :key="index" class="evecal-content cursor-pointer hover:text-link"
@click="openHref(item)">
<img class="evecal-img" :src="item.picture_url" />
<div class="evecal-infodiv">
<div class="evecal-time">{{ formatTime(item.activity_time, 'YYYY-MM-DD HH:MM') }}</div>
<div class="evecal-activity">{{ item.title }}</div>
</div>
</div>
</div>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'even-calendar' });
import { ref, useSlots, computed, type StyleValue } from 'vue';
import { useTimeFormat } from '@/utils/hooks/useTimeFormat';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
const { formatTime } = useTimeFormat();
const { namespace } = getOrgInfo();
interface IData {
calendar?: {
activity_time?: string;
link?: string;
picture_url?: string;
title?: string;
[propName: string]: any;
}[],
visitableReport?: boolean; // 举报 默认可见
allUrl?: string
}
const props = withDefaults(defineProps<IData>(), {
calendar: () => [],
visitableReport: true
});
// 链接跳转
const openHref = (item?: any) => {
window.open(`/organization/${namespace.value}/${item.id}.html`, '_blank', '');
};
const openMoreHref = () => {
window.open(`/organization/${namespace.value}/activity`, '_blank', '');
};
const defaultUrl = computed(() => `/organization/${namespace.value}/activity`);
const slots = useSlots();
const emit = defineEmits<{(e: 'report', /* orgname : string */): void }>();
</script>
<style lang="scss" scoped>
.evecal-title {
display: flex;
align-items: center;
justify-content: space-between;
.icon-comment {
font-size: 16px;
}
.evecal-span {
margin-left: 8px;
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
line-height: 1;
}
}
.evecal-headerleft {
display: flex;
}
.evecal-body {
padding-top: 4px;
.evecal-content {
margin-top: 12px;
color: var(--color-CG800);
font-size: var(--text-sm);
font-weight: 400;
cursor: pointer;
display: flex;
.evecal-img {
width: 120px;
height: 68px;
object-fit: cover;
}
.evecal-infodiv {
flex: 1;
margin-left: 16px;
display: flex;
flex-direction: column;
justify-content: center;
.evecal-time {
color: var(--color-CG600);
font-size: var(--text-sm);
font-weight: 400;
}
.evecal-activity {
margin-top: 4px;
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 400;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div>
<!-- 默认标题 可换插槽 #title-->
<div class="feacon-title" v-if="!slots.title">
<div class="feacon-headerleft">
<img class="w-3 mr-2" :src="iconPaper" />
<span class="feacon-span">精选内容</span>
</div>
<GLink @click="openMoreHref">
<Icon name="gt-all" color="#707A8"></Icon>
</GLink>
</div>
<slot v-else name="title"></slot>
<!-- 内容 -->
<div class="feacon-body">
<div v-for="(item, index) in featured" :key="index" class="feacon-content cursor-pointer hover:text-link" @click="openHref(item)">
{{ item.title }}
</div>
</div>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'featured-content' });
import { ref, useSlots, computed, type StyleValue } from 'vue';
import { useAssets } from '@/utils/hooks/useRepoInit';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
const { iconPaper } = useAssets();
const { namespace } = getOrgInfo();
interface IData {
featured?: {
activity_time?: string;
link?: string;
picture_url?: string;
title?: string;
[propName: string]: any;
}[],
visitableReport?: boolean; // 举报 默认可见
allUrl?: string
}
withDefaults(defineProps<IData>(), {
featured: () => [],
visitableReport: true
});
// 链接跳转
const openHref = (item?: any) => {
window.open(`/organization/${namespace.value}/${item.id}.html`, '_blank', '');
};
const openMoreHref = () => {
window.open(`/organization/${namespace.value}/article`, '_blank', '');
};
const defaultUrl = computed(() => `/organization/${namespace.value}/article`);
const slots = useSlots();
const emit = defineEmits<{(e: 'report', /* orgname : string */): void }>();
</script>
<style lang="scss" scoped>
.feacon-title {
display: flex;
align-items: center;
justify-content: space-between;
.icon-comment {
font-size: 16px;
}
.feacon-headerleft {
display: flex;
}
.feacon-span {
// margin-left: 8px;
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
}
}
.feacon-body {
padding-top: 4px;
}
.feacon-content {
margin-top: 8px;
color: var(--color-CG800);
font-size: var(--text-sm);
font-weight: 400;
}
.feacon-content:hover {
color: var(--color-link);
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<div>
<!-- 默认标题 可换插槽 #title-->
<div class="lanlis-title" v-if="!slots.title">
<Icon name="gt-file-code-c" size="16px" />
<span class="lanlis-span">使用语言</span>
</div>
<slot v-else name="title"></slot>
<!-- 语言标签 -->
<div class="lan-list">
<div class="lan-item" v-for="(item, index) in data" :key="index">
<span class="circle" :style="['background-color:' + item.color]"></span>
<span class="name">{{ item.label }}</span>
</div>
</div>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'language-list' });
import { useSlots, type StyleValue } from 'vue';
interface IData {
data?: {
color: StyleValue | undefined; // language color
label: String;// language name
[propName: string]: any;
}[],
visitableReport?: boolean; // 举报 默认可见
}
withDefaults(defineProps<IData>(), {
data: () => [],
visitableReport: true
});
const slots = useSlots();
const emit = defineEmits<{(e: 'report', /* orgname : string */): void }>();
</script>
<style lang="scss" scoped>
.lanlis-title {
display: flex;
align-items: center;
.icon-comment {
font-size: 16px;
}
.lanlis-span {
margin-left: 8px;
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
line-height: 1;
}
}
.lan-list {
display: flex;
flex-wrap: wrap;
margin-top: 4px;
.lan-item {
margin-right: 13px;
margin-top: 8px;
}
}
.circle {
display: inline-block;
width: 4px;
height: 12px;
background-color: #4b4b4b;
margin-right: 8px;
}
.name {
font-size: var(--text-sm);
color: var(--color-G600);
margin-left: 3px;
}
.report {
color: var(--color-G600);
}
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div>
<!-- 默认标题 可换插槽 #title-->
<div class="latact-title" v-if="!slots.title">
<Icon name="gt-activity-c" size="16px" />
<span class="latact-span">最新活动</span>
</div>
<slot v-else name="title"></slot>
<!-- 走马灯活动 -->
<d-carousel height="169px" class="latact-carousel">
<d-carousel-item v-for="(item, index) in activity" :key="index">
<div class="latact-imgdiv cursor-pointer hover:text-link" @click="openHref(item.link)">
<img class="latact-img" :src="item.picture_url" />
</div>
</d-carousel-item>
</d-carousel>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'latest-activity' });
import { ref, useSlots, type StyleValue } from 'vue';
interface IData {
activity?: {
activity_time?: string;
link?: string;
picture_url?: string;
title?: string;
[propName: string]: any;
}[],
visitableReport?: boolean; // 举报 默认可见
}
withDefaults(defineProps<IData>(), {
activity: () => [],
visitableReport: true
});
// 链接跳转
const openHref = (url?: string) => {
window.open(url, '_blank', '');
};
const slots = useSlots();
const emit = defineEmits<{(e: 'report', /* orgname : string */): void }>();
</script>
<style lang="scss" scoped>
.latact-title {
display: flex;
align-items: center;
.icon-comment {
font-size: 16px;
}
.latact-span {
margin-left: 8px;
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
line-height: 1;
}
}
.latact-carousel {
margin-top: 12px;
:deep(.devui-carousel__dots) {
justify-content: flex-end;
}
.latact-imgdiv {
height: 100%;
}
.latact-img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
</style>

View File

@@ -0,0 +1,272 @@
<template>
<div class="org-header">
<Card class="org-header-info " style="padding: 0; border-radius: 0;">
<div class="org-header-body py-[20px] max-w-[1280px] w-full m-auto min-w-sm px-normal">
<!-- left -->
<div class="flex flex-1 w-0 mr-[48px]">
<!-- 头像 -->
<span v-if="!hideAvatar" class="mr-24">
<GAvatar :src="orgData.avatar" :name="orgData.name" :width="100" :height="100" :is_round="false"
:class="!orgData.avatar ? 'avatar' : 'avatar1'">
</GAvatar>
</span>
<!-- 信息 -->
<div class="info-content flex-1 w-0">
<div class="org-name">
<div class="org-namediv max-w-[380px] text-G900 text-xl mr-[20px] font-bold leading-[28px]">
{{ orgData.name }}
</div>
<d-button :variant="orgStore.isFollow ? undefined : 'solid'"
:color="orgStore.isFollow ? 'secondary' : 'primary'" @click="followClickEvent(orgStore.isFollow)"
class="follow-btn" style="height: 28px;transition: none;">{{
orgStore.isFollow ? '已关注' : '关注' }}</d-button>
</div>
<p class="org-description mt-[12px] h-[40px]" :title="orgData.description || '暂无简介'">{{ orgData.description ||
'暂无简介' }}</p>
<div class="org-contact leading-[20px]">
<span class="org-item mr-[17px]" v-if="orgData.location">
<Icon class="org-icon" name="gt-location"></Icon>
<span class="ml-[10px]">{{ orgData.location }}</span>
</span>
<span class="org-item mr-[17px]" v-if="orgData.email">
<Icon class="org-icon" name="gt-mail"></Icon>
<a class="ml-[10px]" :href="orgData.email ? 'mailto:' + orgData.email : undefined">{{ orgData.email }}</a>
</span>
<span class="org-item" v-if="orgData.home_page">
<Icon class="org-icon" name="gt-link"></Icon>
<GLink class="ml-[10px]" :href="orgData.home_page" v-if="orgData.home_page">{{ orgData.home_page }}
</GLink>
</span>
</div>
</div>
</div>
<!-- right -->
<div class="pt-[51px]">
<div class="flex">
<div class="flex" v-for="(item, index) in orgRData" :key="item.label"
@click="handleNav({ key: item.id, url: item.url })">
<GLink :to="getLink(item)" @click.prevent="() => { }"
class="flex flex-col justify-center items-center min-w-[80px]">
<span class="text-G900 text-lg font-medium leading-[21px]">{{ item.num }}</span>
<span class="text-CG600 text-sm font-normal leading-[20px] mt-[8px]">{{ item.label }}</span>
</GLink>
<div v-if="index != orgRData.length - 1" class="linediv mx-[8px]"></div>
</div>
</div>
</div>
</div>
</Card>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'org-header-info' });
import MemberAvatar from '@/components/MemberAvatarList/index.vue';
import { ref, useSlots, watch, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAccountStore } from '@/stores/user';
import { emitEvent } from '@/utils/eventBus';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
import { useUserInfo } from '@/views/User/hooks/useUserInfo';
import { orgInfoStore } from '@/stores/Org';
interface userAvatarListRow {
username: string;
iam_id: string;
name?: string;
avatar?: string;
avatar_url?: string;
}
interface IProps {
hideAvatar?: boolean // 隐藏头像
}
const props = defineProps<IProps>();
const slots = useSlots();
// 获取接口方法
const { namespace, getOrgMemberListData, followDevCommunity } = getOrgInfo();
// 获取成员列表
getOrgMemberListData(namespace.value);
// 从仓库获取组织信息
const orgStore = orgInfoStore();
const orgData = computed(() => {
return orgStore.orgInfo || {};
});
// 监听用户权限等级
watch(() => orgData.value.my_role, (val) => {
if (val && val.access_level > 0) {
getOrgMemberListData(namespace.value);
}
}, { deep: true });
const orgRData = computed(() => {
return [
{ label: '社区粉丝', num: orgStore.fansTotal || 0, url: '', id: 'orgMember' },
{ label: '项目', num: orgData.value.project_count || 0, url: '', id: 'orgRepos' },
{ label: '文章', num: orgStore.articleTotal || 0, url: `/organization/${namespace.value}/article` }
// { label: 'star', num: orgData.value.star_count || 0, url: '', id: 'orgRepos' }
];
});
// 成员列表跳转
const router = useRouter();
const naviTo = (name: string, params?: any) => {
router.push({ name, params });
};
// 右侧数字跳转
const route = useRoute();
const getLink = (data: any) => {
try {
return router.resolve({
name: data.id
}).fullPath;
} catch (err) {
// ignore
}
};
// 点击tab跳转路由
const handleNav = (val: any) => {
let name = val.key;
if (!name) {
name = orgStore.communityUrl;
window.open(val.url);
} else {
const query = name === 'orgMember' ? { isOrgToFans: 'isOrgToFans' } : {};
router.push({ name: name, query: query });
}
};
// 获取用户信息
const { userInfo } = useUserInfo();
// 点击关注
// 关注点击
/* 权限验证 */
const account = useAccountStore();
// 关注dev社区
const followClickEvent = (val: boolean) => {
if (!account.isLogin) {
emitEvent('login', { triggerType: '关注组织' });
return false;
}
followDevCommunity(val);
};
</script>
<style lang="scss" scoped>
.org-header {
background: linear-gradient(180deg, #FBFBFB 0%, #FFFFFF 100%);
.org-header-info {
width: 100%;
.org-header-body {
display: flex;
overflow: hidden;
justify-content: space-between;
}
}
}
.orghea-fw-bold {
font-weight: 500;
}
.orghea-lh-32 {
line-height: 32px;
}
.info-content {
.org-name {
display: flex;
// justify-content: space-between;
}
.org-namediv {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.org-namespan {
display: inline-block;
color: var(--color-G900);
font-size: var(--text-2xl);
font-weight: 500;
line-height: 32px;
letter-spacing: 0em;
text-align: left;
}
.org-subtext {
display: inline-block;
height: 20px;
font-size: var(--text-sm);
font-weight: 400;
color: var(--color-CG600);
line-height: 20px;
}
}
.linediv {
width: 1px;
height: 41px;
background: #E4E9F0;
}
.avatar {
:deep(.devui-avatar--style) {
border: 0.5px solid #d3d3d3;
}
}
.avatar1 {
border: 0.5px solid #d3d3d3;
border-radius: 4px;
overflow: hidden;
}
.org-description {
width: 100%;
color: var(--color-CG600);
font-size: var(--text-sm);
font-weight: 400;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
word-break: break-word;
}
.org-contact {
display: flex;
align-items: center;
flex-wrap: wrap;
word-break: break-all;
color: var(--color-CG600);
font-size: var(--text-sm);
font-weight: 400;
.org-item {
line-height: 20px;
display: inline-flex;
align-items: center;
.org-icon {
min-width: 16px;
}
:deep(a) {
color: #707A87;
}
}
}
</style>

View File

@@ -0,0 +1,200 @@
<template>
<Panel class="docinf overflow-hidden" v-if="showReadme">
<template #header>
<div class="docinf-lefthead">
<Icon name="gt-file-c" class="docinf-icon"></Icon>
<span class="docinf-rspan">README.md</span>
</div>
</template>
<template #headerRight>
<span v-if="isAdmin" class="cursor-pointer"
@click="naviTo"><Icon
name="gt-edit"></Icon></span>
</template>
<div ref="eleRef" class="docinf-content" v-loading="readmeLoading">
<MdRender v-model="readmeText"></MdRender >
</div>
</Panel>
<!-- <GLink v-if="visitableReport" @click="emit('report')" class="report">举报</GLink> -->
</template>
<script setup lang="ts">
defineOptions({ name: 'docker-info' });
import { useShowMore } from '@/utils/hooks/useShowMore';
import MdRender from '@/components/MdRender/index.vue';
import { ref, reactive, useSlots, watch, onUnmounted } from 'vue';
import { storeToRefs } from 'pinia';
import Panel from '@/components/Panel/index.vue';
import { useRouter } from 'vue-router';
import { getRepo, getRepoReadme } from '@/api/repo';
import { reqCatch } from '@/utils/catch';
import utf8 from 'crypto-js/enc-utf8';
import Base64 from 'crypto-js/enc-base64';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
import { orgInfoStore } from '@/stores/Org';
// 获取组织path方法
const { namespace } = getOrgInfo();
interface IData {
// full_path?: string, // 组织路径
visitableReport?: boolean; // 举报 默认可见
}
interface RepoData {
name?: string,
visibility?: string,
readme_url?: string,
tag_count?: number,
watch_count?: number,
forks_count?: number,
star_count?: number,
branch_count?: number,
open_merge_requests_count?: number,
http_url_to_repo?: string,
ssh_url_to_repo?: string,
description?: string,
tag_list?: [],
web_url?: string,
default_branch?: string,
namespace?: {}
[propName: string]: any;
}
const { isAdmin } = storeToRefs(orgInfoStore());
const props = withDefaults(defineProps<IData>(), {
visitableReport: true
});
const eleRef = ref(null);
const { stop } = useShowMore(eleRef);
onUnmounted(() => {
stop && stop();
});
// 监听组织路径变化
// 监听路由后面需要优化成路由监听
let lastNameSpace = namespace.value;
watch(() => namespace.value, (val, oldVal) => {
if (val !== lastNameSpace) {
showReadme.value = false;
readmeText.value = '';
getProInfo();
}
lastNameSpace = val;
}, {
deep: true
});
// 获取rederme文件
const readmeLoading = ref<boolean>(true);
const readmeText = ref<string>('');
const showReadme = ref<boolean>(false);// 是否展示readme
const redUrl = ref<string>();
const getProReadme = async() => {
if (!namespace.value) return;
const proName = namespace.value.split('%2F');
const params = {
repoId: `${namespace.value}%2F${proName[proName.length - 1]}`
};
const { data, error } = await getRepoReadme(params);
readmeLoading.value = false;
if (!error && data?.data) {
readmeText.value = utf8.stringify(Base64.parse(data.data.content || ''));
redUrl.value = data.data.readme_url;
showReadme.value = !!data.data.readme_url;
}
};
getProReadme();
// const slots = useSlots();
// const emit = defineEmits<{(e: 'report', /* orgname : string */): void }>();
const router = useRouter();
const naviTo = () => {
// router.push({ name, params });
if (redUrl.value) {
window.location.assign(redUrl.value);
}
};
const mode = ref('readonly');
</script>
<style lang="scss" scoped>
.docinf {
.docinf-lefthead {
line-height: 20px;
display: flex;
align-items: center;
.docinf-icon {
margin-right: 20px;
}
.docinf-dspan {
color: var(--color-CG600);
font-size: var(--text-sm);
font-weight: 500;
margin-right: 4px;
}
.docinf-rspan {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
}
}
.docinf-content {
position: relative;
max-height: 400px;
overflow: auto;
&.show-all {
max-height: 100%;
}
&-btn {
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
z-index: 2;
width: 100%;
text-align: center;
background: #fff;
&:hover {
cursor: pointer;
}
}
.docinf-section {}
.docinf-section1 {
margin-top: 32px;
}
.docinf-section-title {
color: var(--color-G900);
font-size: var(--text-2xl);
font-weight: 500;
}
.docinf-section-line {
width: 100%;
height: 1px;
background: #F0F1F2;
margin-top: 16px;
}
.docinf-section-content {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 400;
margin-top: 16px;
}
.docinf-section-origin {
padding-left: 16px;
}
.docinf-section-subtitle {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
margin-top: 16px;
}
}
}
</style>

View File

@@ -0,0 +1,371 @@
<template>
<div>
<Panel class="selite overflow-hidden" :blank="false">
<template #header>
<div class="selite-lefthead">
<Icon name="gt-folder-c" size="16px" class="mr-20" />
<span class="selite-dspan" v-if="repoParams.repoHandpickList[0]">精选项目</span>
<span class="selite-dspan" v-else-if="popularProjectList[0]">热门项目</span>
<span class="selite-dspan" v-else-if="loadingStatus.reposLoading"></span>
<span class="selite-dspan" v-else>热门项目</span>
</div>
</template>
<template #headerRight>
<repo-select-modal @update="onUpdate" @change="onChange" :setType="'group_project'" :repoParams="repoParams" :hack-req-func="getOrgProjectAllListDataNoPage"
v-if="isAdmin" />
</template>
<div class="selite-content">
<DataPanel v-if="repoParams.repoHandpickList[0]" :loading="loadingStatus.reposLoading" :empty="!repoParams.repoHandpickList?.length" animation skeleton
class="px-[10px] py-[20px] w-full" :card="false">
<swiper :slidesPerView="'auto'" :spaceBetween="0" :cssMode="true" :navigation="true" :mousewheel="true"
:keyboard="true" :modules="modules" class="mySwiper">
<swiper-slide v-for="(item, index) in repoParams.repoHandpickList" :key="item.id">
<div class="flex px-[10px] w-[370px] overflow-hidden swiper-item-repo">
<repo-item class="w-[350px] border-none" :iconHandleList="item.iconHandleList" :id="item.id"
:imgSrc="item.imgSrc" hideStar :title="item.title" :tag-list="item.tagList" :desc="item.desc"
:isStar="item.isStar" :web_url="item.web_url"
:class="{ 'is-last': index === repoHandpickLists1.length - 1 }" @handle-star="({isStar}) => item.isStar = isStar" />
</div>
</swiper-slide>
</swiper>
</DataPanel>
<DataPanel v-else :loading="loadingStatus.reposLoading" :empty="!popularProjectList[0]" animation skeleton
class="px-[10px] py-[20px] w-full" :card="false">
<swiper :slidesPerView="'auto'" :spaceBetween="0" :cssMode="true" :navigation="true" :mousewheel="true"
:keyboard="true" :modules="modules" class="mySwiper">
<swiper-slide v-for="(item, index) in popularProjectList" :key="item.id">
<div class="flex px-[10px] w-[370px] overflow-hidden swiper-item-repo">
<repo-item class="w-[350px] border-none" :iconHandleList="item.iconHandleList" :hideRight="true" :id="item.id"
:imgSrc="item.imgSrc" hideStar :title="item.title" :tag="item.tag" :desc="item.desc"
:isStar="item.isStar" :web_url="item.web_url"
:class="{ 'is-last': index === popularProjectList.length - 1 }" @handle-star="({isStar}) => item.isStar = isStar" />
</div>
</swiper-slide>
</swiper>
</DataPanel>
</div>
</Panel>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'selected-items' });
import { ref, reactive, watch, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import Panel from '@/components/Panel/index.vue';
import RepoItem, { dataHandler } from '@/components/RepoItem/index.vue';
import RepoSelectModal from '@/views/User/components/repo-select-modal.vue';
import { useUserInfo } from '@/views/User/hooks/useUserInfo';
import { emitEvent } from '@/utils/eventBus';
import { storeToRefs } from 'pinia';
import { starRepo, unstarRepo } from '@/api/repo';
import { getOrgHandpickProjectList, getOrgProjectList } from '@/api/org/index';
import { reqCatch } from '@/utils/catch';
import * as types from '@/api/org/types';
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
import { orgInfoStore } from '@/stores/Org';
import { useTimeFormat } from '@/utils/hooks/useTimeFormat';
const { formatTimeFromNow } = useTimeFormat();
// Import Swiper Vue.js components
import { Swiper, SwiperSlide } from 'swiper/vue';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
// import required modules
import { Navigation, Pagination, Mousewheel, Keyboard } from 'swiper/modules';
const modules = [Navigation, Pagination, Mousewheel, Keyboard];
// 获取组织path方法
const { namespace, loadingStatus } = getOrgInfo();
// 精选项目
const { isSelf, userInfo } = useUserInfo();
const route = useRoute();
const { isAdmin } = storeToRefs(orgInfoStore());
// 获取组织下精选项目
const repoParams = reactive<{
repoHandpickList: any,
repoAllList: any
}>({
repoHandpickList: [],
repoAllList: [] // 不带分页的所有项目
});
// 热门项目或最新项目
const popularProjectList = ref<RepoItemData[]>([]);
// const repoPageAllList = ref<any>([]);
const getOrgProjectListData = async() => {
const params: types.commonGroupReqType = {
type: 'group_project',
simple: false,
group_id: namespace.value
};
loadingStatus.reposLoading = true;
const { data, error } = await reqCatch(getOrgHandpickProjectList, params);
if (!error) {
if (data) {
const formatData = data?.data?.map((item: any) => {
const allInfo = {
...item.data,
object_id: item.object_id,
resource_id: item.resource_id,
type: item.type
};
const { fork_count, ...newInfo } = allInfo; // 去掉fork数量
return newInfo;
});
repoParams.repoHandpickList = dataHandler(formatData);
repoParams.repoHandpickList.forEach(e => {
e.iconHandleList = e.iconHandleList?.filter(item => item.icon !== 'gt-fork' && item.icon !== 'gt-license'); // 过滤 fork 和 协议
});
if (!formatData[0]) {
await getPopularProjectList();
}
}
}
loadingStatus.reposLoading = false;
};
/**
* 获取最新项目列表
* 当没有精选项目时展示最近的6个项目 支持star筛选时则使用star最多的项目
* 使用star筛选
*/
const getPopularProjectList = async() => {
const result = await reqCatch(getOrgProjectList, {
orgId: namespace.value, page: 1, per_page: 6,
order_by: 'star_count',
simple: false,
visibility: 'public'
});
if (!result.error) {
const { content = [] } = result.data?.data;
popularProjectList.value = content.filter((item) => !!item && (item.id || item.resource_id))
.map((item) => {
const langs = (item.main_repository_language || []).filter((lang) => !!lang);
const lang = langs[0] || '';
const langColor = langs[1] || 'red';
const res = {
id: item.id || item.resource_id || '',
imgSrc: '',
title: item.name || '',
desc: item.description || '暂无简介',
isStar: item.starred || false,
tag: item.visibility,
to: `/${item.path_with_namespace}`,
web_url: `/${item.path_with_namespace}`,
iconHandleList: [
{ icon: 'icon-dot-status', value: lang, type: 'language', iconColor: langColor, label: '', to: '' },
{ icon: 'gt-star', value: item.star_count || 0, label: '', to: '', iconColor: '#707A87' },
// { icon: 'gt-fork', value: item.forks_count || 0, label: '', to: '', iconColor: '#707A87' },
// { icon: 'gt-license', value: item.license?.key, label: '', to: '', iconColor: '#707A87' },
{ icon: 'gt-date', value: formatTimeFromNow(item.updated_at), label: '', to: '', iconColor: '#707A87' }
]
};
if (!lang) {
res.iconHandleList.splice(0, 1);
}
if (!item.license || !item.license?.key) {
res.iconHandleList = res.iconHandleList.filter(item => item.icon !== 'gt-license');
}
return res;
});
}
};
getOrgProjectListData();
const repoHandpickLists1 = computed(() => {
return repoParams.repoHandpickList.filter((item: any, index: number) => index < 4);
});
// 获取组织下所有项目
const pager = reactive({
total: 0,
pageSize: 10,
pageIndex: 1
});
// 监听组织路径变化
// 监听路由后面需要优化成路由监听
let lastNameSpace = namespace.value;
watch(() => namespace.value, (val, oldVal) => {
if (val !== lastNameSpace) {
onUpdate();
}
lastNameSpace = val;
}, {
deep: true
});
// 项目设置后更新
const onUpdate = () => {
getOrgProjectListData();
};
// 点击精选按钮
let pIndex = 1;
const onChange = () => {
pIndex = 1;
getOrgProjectAllListDataNoPage();
};
// 获取项目
const getOrgProjectAllListDataNoPage = async(search?: string, callback?: Function) => {
const params: types.commonGroupReqType = {
search: search,
orgId: namespace.value,
simple: false,
include_subgroups: true,
page: pIndex,
per_page: 9999
};
const { data, error } = await reqCatch(getOrgProjectList, params);
if (!error) {
if (data) {
pager.total = data.data.total;
// Object.assign(orgData, res.data.data);
const formatData = data?.data?.content.map((item: any) => {
return {
...item,
object_id: item.object_id,
resource_id: item.resource_id,
type: item.type
};
});
const ary = dataHandler(formatData);
repoParams.repoAllList = [...ary];
callback?.();
}
}
};
// 处理项目star
const loading = ref(false);
const toggleRepoStar = ({ id, isStar }) => {
if (!userInfo || !userInfo.username) {
emitEvent('login');
return false;
}
if (loading.value) return false;
loading.value = true;
if (isStar) {
unstarRepo({ repoId: id })
.then(() => {
onUpdate();
}).finally(() => {
loading.value = false;
});
} else {
starRepo({ repoId: id })
.then(() => {
onUpdate();
}).finally(() => {
loading.value = false;
});
}
};
</script>
<style lang="scss" scoped>
.selite {
.selite-lefthead {
line-height: 20px;
display: flex;
align-items: center;
.selite-icon {
margin-right: 21px;
}
.selite-dspan {
color: var(--color-G900);
font-size: var(--text-sm);
font-weight: 500;
}
}
.selite-content {
.is-last {
:deep(.g-repo-item) {
border-bottom: none;
}
}
:deep(.g-repo-item) {
border-top: none;
border-left: none;
border-right: none;
border-radius: 0;
box-shadow: none;
.g-repo-item-desc{
height: 60px;
-webkit-line-clamp: 3;
}
}
:deep(.swiper-slide) {
width: 370px !important;
}
}
.selite-flex {
display: flex;
align-items: center;
justify-content: center;
}
:deep(.devui-carousel) {
width: 100%;
}
:deep(.swiper-button-prev:after) {
display: none;
}
:deep(.swiper-button-next:after) {
display: none;
}
:deep(.swiper-button-next) {
border-radius: 50%;
background: url('@/assets/imgs/org/dev-right.png');
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(64, 64, 64, 0.25);
width: 40px;
height: 40px;
backdrop-filter: blur(10px);
pointer-events: auto;
}
:deep(.swiper-button-prev) {
border-radius: 50%;
background: url('@/assets/imgs/org/dev-left.png');
background-position: center center;
background-repeat: no-repeat;
background-color: rgba(64, 64, 64, 0.25);
width: 40px;
height: 40px;
backdrop-filter: blur(10px);
pointer-events: auto;
}
}
.swiper-item-repo {
:deep(.repo-title) {
@apply flex items-center whitespace-nowrap text-ellipsis overflow-hidden;
a {
@apply whitespace-nowrap text-ellipsis overflow-hidden flex-initial;
}
}
:deep(.g-repo-item-desc) {
max-width: none;
}
}
</style>