搜索结果列表页面开发
This commit is contained in:
254
src/views/Org/components/ActivityList.vue
Normal file
254
src/views/Org/components/ActivityList.vue
Normal 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>
|
||||
175
src/views/Org/components/CommunityDynamics.vue
Normal file
175
src/views/Org/components/CommunityDynamics.vue
Normal 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>
|
||||
|
||||
272
src/views/Org/components/DevNewActivityList.vue
Normal file
272
src/views/Org/components/DevNewActivityList.vue
Normal 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>
|
||||
256
src/views/Org/components/DevTechnicalArticle.vue
Normal file
256
src/views/Org/components/DevTechnicalArticle.vue
Normal 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>
|
||||
133
src/views/Org/components/EventCalendar.vue
Normal file
133
src/views/Org/components/EventCalendar.vue
Normal 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>
|
||||
97
src/views/Org/components/FeaturedContent.vue
Normal file
97
src/views/Org/components/FeaturedContent.vue
Normal 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>
|
||||
88
src/views/Org/components/LanguageList.vue
Normal file
88
src/views/Org/components/LanguageList.vue
Normal 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>
|
||||
82
src/views/Org/components/LatestActivity.vue
Normal file
82
src/views/Org/components/LatestActivity.vue
Normal 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>
|
||||
272
src/views/Org/components/OrgHeaderInfo.vue
Normal file
272
src/views/Org/components/OrgHeaderInfo.vue
Normal 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>
|
||||
200
src/views/Org/components/ReadmeInfo.vue
Normal file
200
src/views/Org/components/ReadmeInfo.vue
Normal 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>
|
||||
371
src/views/Org/components/SelectedItems.vue
Normal file
371
src/views/Org/components/SelectedItems.vue
Normal 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>
|
||||
Reference in New Issue
Block a user