搜索结果列表页面开发

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,64 @@
<template>
<d-dropdown class="g-header-create" :position="['bottom-end']" align="end">
<d-button class="border-none w-84 h-32 btn-add">
<GIcon name="gt-plane-add" size="16px" color="#9C9DB4" />
<span>新建</span>
</d-button>
<template #menu>
<div class="py-10 px-4">
<OptionLink v-for="item in linkList" :key="item.label" v-bind="item" @click="handleClick(item, $event)">
{{ item.label }}
</OptionLink>
</div>
</template>
</d-dropdown>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { orgInfoStore } from '@/stores/Org/index';
import { Message } from 'vue-devui';
const orgStore = orgInfoStore();
const linkList = ref([
{ label: '新建项目', to: { name: 'newRepo', query: { position: 'nav_top' }}},
{ label: '导入项目URL', to: { name: 'importRepo' }},
{ label: '迁移项目', to: { name: 'migrateRepo' }, disabled: false },
{ label: '新建组织', routeName: 'newOrg' }
]);
const route = useRoute();
const router = useRouter();
const handleClick = (linkItem, event) => {
if (linkItem?.routeName === 'newOrg') {
if (!orgStore.isCreateOrg) { // 超过用户可管理组织数量,禁止进入创建组织页面
Message.error('你最多可以创建或管理 5 个组织,无法创建新的组织。');
return;
} else {
router.push({name: 'newOrg'});
}
}
// 针对创建项目如果通过toolbar进入项目创建页面强制刷新数据
if (linkItem?.to?.name === 'newRepo' && linkItem?.to?.name === route.name) {
window.location.reload();
event.stopPropagation();
}
};
</script>
<style lang="scss" scoped>
.btn-add {
background-color: #000;
color: #fff !important;
width: 84px;
font-size: 14px;
line-height: 20px;
span {
margin-left: 8px;
}
&:hover {
color: #fff;
background-color: #333;
}
}
</style>

View File

@@ -0,0 +1,176 @@
<template>
<d-dropdown
v-if="!isMobile"
class="g-entrance"
:position="['bottom-start']"
align="start"
@toggle="handleToggle"
>
<div class="g-entrance-trigger cursor-pointer">
<GIcon name="gt-list-view" size="20px" />
</div>
<template #menu>
<EntranceMenu
:linkList="linkList"
:blockList="blockList"
@handleToggle="handleToggle($event)"
/>
</template>
</d-dropdown>
<GIcon
v-if="isMobile"
class=""
name="gt-hamburger"
size="20px"
@click="handleToggle"
/>
<d-drawer v-if="isMobile" v-model="visible" position="left">
<EntranceMenu
class="p-5"
:linkList="linkList"
:blockList="blockList"
@handleToggle="handleToggle($event)"
/>
</d-drawer>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { getRepos } from '@/api/repo';
import { getMyGroupsList } from '@/api/user';
import { useAccountStore, entranceData } from '@/stores/user';
import EntranceMenu from './EntranceMenu.vue';
import { storeToRefs } from 'pinia';
defineProps({
isMobile: {
default: false
}
});
const user = useAccountStore();
const { username } = user.accountInfo;
const { repoList, orgList } = storeToRefs(entranceData());
const visible = ref(false);
const handleToggle = (isVisible?: boolean) => {
visible.value = !!isVisible;
if (visible.value) init();
};
// 头部模块连接
const linkList = ref([
{ title: '首页', icon: 'gt-home-c', href: '/' },
{ title: '应用市场', icon: 'gt-package-c', to: { name: 'home' }, disabled: true }
]);
/* 设置我的组织和项目列表 */
const repoData = ref({
id: 'repo',
title: '最近活跃项目',
list: [],
avatarRound: true,
loading: false,
link: {
to: {
name: 'settingRepo',
params: {
namespace: username
}
}
}
});
const orgData = ref({
id: 'org',
title: '加入组织',
list: [],
avatarRound: false,
loading: false,
link: {
to: {
name: 'settingOrganization',
params: {
namespace: username
}
}
}
});
const blockList = computed(() => [repoData.value, orgData.value]);
const MAX_LIST = 5;
const initRepoData = async() => {
repoData.value.loading = true;
const repoParams = {
order_by: 'last_activity_at',
sort: 'desc',
membership: true
};
if (!repoList.value?.length) {
const res = await getRepos(repoParams);
repoList.value = res?.data?.data?.content;
}
repoData.value.list = (repoList.value || []).slice(0, MAX_LIST).map((item:{name_with_namespace?:string, name?:string, namespace?:string}) => ({
...item,
label: item.name_with_namespace,
avatarText: item.name,
to: {
path: `/${item.namespace}`
}
}));
repoData.value.loading = false;
};
const initOrgData = async() => {
orgData.value.loading = true;
const orgParams = {
page: 1,
per_page: 10
};
if (!orgList.value?.length) {
const res = await getMyGroupsList(orgParams);
orgList.value = res?.data?.content;
}
orgData.value.list = (orgList.value || []).slice(0, MAX_LIST).map((item:{full_name:string, name:string, full_path:string}) => ({
...item,
label: item.full_name,
avatarText: item.name,
to: {
name: 'homepage',
params: {
namespace: item.full_path
}
}
}));
orgData.value.loading = false;
};
const init = () => {
initRepoData();
initOrgData();
};
</script>
<style lang="scss" scoped>
@import 'devui-theme/styles-var/devui-var.scss';
.g-entrance-menu {
padding: 20px;
width: 260px;
line-height: 32px;
&-trigger {
height: 32px;
width: 32px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: background-color ease-out 0.5s;
&:hover {
background-color: #fbfbfb;
}
}
}
</style>

View File

@@ -0,0 +1,118 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
type LinkItem = {
title: string;
icon: string;
to: { name: string };
disabled?: boolean;
};
type BlockItem = {
id: string;
title: string;
list: any[];
avatarRound: boolean;
link: {
to: {
name: string;
params: {
namespace?: string;
};
};
};
};
defineProps<{
linkList: LinkItem[];
blockList: BlockItem[];
}>();
const emit = defineEmits<{(event: 'handleToggle', value: boolean): void;
}>();
const router = useRouter();
// repo\org切换强制页面重载
const handleLinkClick = (event, data) => {
const href = router.resolve(data.to).fullPath;
window.open(href, '_self');
emit('handleToggle', false);
event?.stopPropagation();
};
</script>
<template>
<div class="g-entrance-menu">
<div class="g-entrance-menu-links">
<OptionLink v-for="item in linkList" :key="item.title" v-bind="item">
<GIcon :name="item.icon" />{{ item.title }}
</OptionLink>
</div>
<div class="g-entrance-menu-block" v-for="block in blockList" :key="block.id">
<div class="g-entrance-menu-block-title">
<span>{{ block.title }}</span>
<GLink v-bind="block.link">
<GIcon name="gt-more-operate" />
</GLink>
</div>
<div class="g-entrance-menu-block-list">
<DataPanel :loading="block.loading" :empty="!block.list?.length" :card="false" skeleton>
<OptionLink
v-for="item in block.list"
:key="item.name"
v-bind="item"
@click="handleLinkClick($event, item)"
>
<GAvatar
:src="item.avatar"
:name="item.avatarText"
:width="16"
:height="16"
:is_round="block.avatarRound"
></GAvatar>
<d-tooltip :content="item.label" position="right" :mouse-enter-delay="800">
<div class="ellipsis">{{ item.label }}</div>
</d-tooltip>
</OptionLink>
<template #empty>
<div class="py-2 flex-center opacity-50">暂无数据</div>
</template>
<template #loading>
<d-skeleton :rows="5" />
</template>
</DataPanel>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
@import 'devui-theme/styles-var/devui-var.scss';
.g-entrance-menu {
&-links {
display: flex;
flex-direction: column;
}
&-block {
display: flex;
flex-direction: column;
border-top: 1px solid var(--color-border-light);
margin-top: 16px;
&:last-child {
padding-bottom: 0;
}
&-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
font-weight: 500;
color: #2d2d2e;
line-height: 32px;
margin-top: 8px;
}
}
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<router-link to="/notice?status=all&message_type=mr">
<d-badge class="g-notice" :count="counts" :offset="[-2, 4]" :hidden="!counts" status="danger">
<GIcon name="gt-line-notice" />
</d-badge>
</router-link>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { getMessageCount } from '@/api/notice';
import { addEventListener, offEvent } from '@/utils/eventBus';
import debounce from 'lodash/debounce';
const counts = ref(0);
const getCounts = () => {
getMessageCount().then((result) => {
counts.value = result.data?.reduce((prev: number, curr: Record<string, any>) => {
return prev + curr.un_read_count;
}, 0);
});
};
onMounted(() => {
getCounts();
addEventListener('updateNotice', debounce((count: number) => {
counts.value = count;
}, 500));
});
onBeforeUnmount(() => {
offEvent('updateNotice');
});
</script>
<style lang="scss" scoped>
@import 'devui-theme/styles-var/devui-var.scss';
.g-notice {
cursor: pointer;
margin: 0 8px;
&-list {
display: flex;
flex-direction: column;
padding: 8px;
&-item {
width: 280px;
padding: 8px 0;
&-title {
font-weight: 500;
}
&-content {
font-weight: 400;
}
}
}
}
</style>

View File

@@ -0,0 +1,25 @@
# Header
### 说明
- 全局通用Header组件
``` js
<Header />
<Header>
<template #info>
这是左侧插槽
</template>
<template #menu>
这是右侧插槽一般用于插入菜单Tab组件
</template>
</Header>
```
### Slots
| 名称 | 说明 |
|---------------------|---------------------------------------------|
| info | 第二行左侧,常用语显示用户信息等 |
| menu | 第二行右侧常用于定义路由Tab等 |

View File

@@ -0,0 +1,84 @@
<template>
<d-input
:key="$route.path + $route.query?.val" id="golbalSearch" class="g-header-search"
v-model.trim="searchStr" placeholder="输入搜索内容..." @keyup.enter="search" @clear="handleClear" clearable >
<template #prepend>
<d-select v-model="searchType" :options="options"></d-select>
</template>
<template #append>
<d-icon name="search" style="font-size: inherit;" />
</template>
</d-input>
</template>
<script setup>
import { ref, onMounted, watch, onUnmounted, reactive } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import microApp from '@micro-zoe/micro-app';
const route = useRoute();
const router = useRouter();
const searchStr = ref(route?.query?.val || '');
const searchType = ref('License');
const options = reactive(['License']);
const placeholder = ref('「开搜」一搜即达开发者的AI搜索');
const search = () => {
if (!searchStr.value) {
document.querySelector('#golbalSearch').blur();
return;
}
router.push({ name: 'search', query: { val: searchStr.value || '', type: route?.query?.type || 'License' }});
};
const handleClear = () => {
router.push({ name: 'home' });
};
watch(() => route.query?.val, (newVal) => {
microApp.setData('micoro-app-homeweb-app', { type: 'search', data: newVal || '' });
});
const handleDataChange = ({ name, type, data }) => {
if (name === 'micoro-app-homeweb-app') {
if (type === 'keyword-change') {
searchStr.value = data;
}
if (type === 'route-change') {
//
}
}
};
onMounted(() => {
});
onUnmounted(() => {
});
</script>
<style lang="scss" scoped>
@import 'devui-theme/styles-var/devui-var.scss';
.g-header-search {
border: 1px solid #5E7CE0;
background: #FFF;
//box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.05) inset;
&-icon {
opacity: .5;
transition: opacity .5s ease;
}
.devui-input--focus {
.g-header-search-icon {
opacity: 1;
}
}
::v-deep .devui-input-slot__prepend{
width: 140px;
}
::v-deep .devui-input-slot__append{
width: 40px;
}
}
</style>

View File

@@ -0,0 +1,86 @@
<template>
<d-input
:key="$route.path + $route.query?.val"
id="golbalSearch"
class="g-header-search"
v-model.trim="searchStr"
:placeholder="placeholder"
clearable
@keyup.enter="search"
@focus="placeholder = '「开搜」:输入搜索内容...'"
@blur="placeholder = '「开搜」一搜即达开发者的AI搜索'"
autocomplete="off"
>
<template #prefix>
<GIcon name="gt-search" class="g-header-search-icon" />
</template>
</d-input>
</template>
<script setup>
import { ref, onMounted, watch, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import microApp from '@micro-zoe/micro-app';
const route = useRoute();
const router = useRouter();
const searchStr = ref(route?.query?.val || '');
const placeholder = ref('「开搜」一搜即达开发者的AI搜索');
const search = () => {
if (!searchStr.value) {
document.querySelector('#golbalSearch').blur();
return;
}
if (microApp.router.current.has('micoro-app-homeweb-app')) {
microApp.router.push({
name: 'micoro-app-homeweb-app',
path: router.resolve({ name: 'search', query: { val: searchStr.value, type: route?.query?.type || 'repo' }}).href
});
} else {
router.push({ name: 'search', query: { val: searchStr.value || '', type: route?.query?.type || 'repo' }});
}
};
watch(() => route.query?.val, (newVal) => {
microApp.setData('micoro-app-homeweb-app', { type: 'search', data: newVal || '' });
});
const handleDataChange = ({ name, type, data }) => {
if (name === 'micoro-app-homeweb-app') {
if (type === 'keyword-change') {
searchStr.value = data;
}
if (type === 'route-change') {
//
}
}
};
onMounted(() => {
microApp.addDataListener('micoro-app-homeweb-app', handleDataChange, false);
});
onUnmounted(() => {
microApp.removeDataListener('micoro-app-homeweb-app', handleDataChange);
});
</script>
<style lang="scss">
@import 'devui-theme/styles-var/devui-var.scss';
.g-header-search {
background: #FFF;
box-shadow: 0px 2px 5px 1px rgba(0, 0, 0, 0.05) inset;
&-icon {
opacity: .5;
transition: opacity .5s ease;
}
.devui-input--focus {
.g-header-search-icon {
opacity: 1;
}
}
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<div class="g-user-avatar flex-center ml-1" @click="toggleDropDown">
<GAvatar :name="accountInfo.nickname" :src="accountInfo.avatar"></GAvatar>
</div>
<d-drawer v-model="visible" class="g-user-drawer-container">
<div class="g-user-drawer">
<div class="g-user-drawer-info">
<div class="g-user-avatar">
<GAvatar :name="accountInfo.nickname" :src="accountInfo.avatar"></GAvatar>
</div>
<div class="g-user-drawer-info-list">
<div class="g-user-drawer-info-list-name">
<span class="break-all">{{ accountInfo.nickname }}</span>
<GIcon class="close-icon" name="gt-close" size="12" @click="handleClose" />
</div>
<div class="g-user-drawer-info-list-id break-all">@{{ accountInfo.username }}</div>
</div>
</div>
<div class="g-user-drawer-block" v-for="(block, index) in blockList" :key="index">
<OptionLink v-for="item in block" :key="item.id" v-bind="item" @click="handleClick(item)">
<GIcon v-if="item.icon" :name="item.icon" size="14px" />{{ item.label }}
</OptionLink>
</div>
</div>
</d-drawer>
</template>
<script setup>
import { ref } from 'vue';
import { useAccount } from '@/utils/hooks/useAccount';
import { toLogout } from '@/api/user';
import { devLogout } from '@/api/org/devIndex';
import { useRouter, useRoute } from 'vue-router';
import { reqCatch } from '@/utils/catch';
import { orgInfoStore } from '@/stores/Org/index';
import { repoInfoStore } from '@/stores/Repo';
import { storeToRefs } from 'pinia';
const { accountInfo, RemoveInfo } = useAccount();
const { repoInfo } = storeToRefs(repoInfoStore()); // pinia直接解构会失去响应式
const { orgInfo } = storeToRefs(orgInfoStore()); // pinia直接解构会失去响应式
const router = useRouter();
const route = useRoute();
const visible = ref(false);
const toggleDropDown = (isShow) => {
if (typeof isShow === 'boolean') {
visible.value = isShow;
} else {
visible.value = !visible.value;
}
};
const logout = async() => {
const res = await toLogout();
await reqCatch(devLogout, {}); // 清除社区登录
if (!res.error) {
RemoveInfo();
// 有权限刷新当前页,无权限需要登录返回首页
if (route.meta?.needLogin || (route.meta?.type === 'repo' && repoInfo.value?.visibility === 'private') || orgInfo.value?.visibility === 'private') {
router.push('/');
} else {
router.go(0);
}
}
};
const handleClick = (item) => {
if (item?.action) {
item.action();
}
toggleDropDown(false);
};
const handleClose = () => {
toggleDropDown(false);
};
// 使用router-link跳转默认参数结构如下
const DEFAULT_ROUTER_PARAMS = Object.freeze({
name: '',
params: {
namespace: accountInfo.value.username
},
query: {
}
});
// 获取router跳转参数
const getRouterLink = params => {
if (typeof params === 'string') {
return {
...DEFAULT_ROUTER_PARAMS,
name: params
};
}
return {
...DEFAULT_ROUTER_PARAMS,
...params
};
};
const blockList = ref([
[
{ id: 'myPage', label: '我的主页', to: getRouterLink('homepage') }
],
[
{ id: 'dashboard', label: '工作台', icon: 'gt-member-c', to: getRouterLink('dashboard') },
{ id: 'myIssues', label: '我的 Issue', icon: 'gt-issue-c', to: getRouterLink('issues') },
{ id: 'myDiscuss', label: '我的讨论', icon: 'gt-comment-c', to: getRouterLink('discussionCreated') },
{ id: 'myMR', label: '我的合并请求', icon: 'gt-me-merge-c', to: getRouterLink('merge') },
{ id: 'notice', label: '消息通知', icon: 'gt-remind-c', to: getRouterLink({ name: 'notice', query: { status: 'all' }}) }
],
[
{ id: 'myOrg', label: '我的组织', icon: 'gt-organizations-c', to: getRouterLink({ name: 'settingOrganization' }) },
{ id: 'myRepo', label: '我的项目', icon: 'gt-folder-c', to: getRouterLink('userRepos') },
{ id: 'myStar', label: '我的 Star', icon: 'gt-star-c', to: getRouterLink('userStars') }
],
[
{ id: 'personalSetting', label: '个人设置', icon: 'gt-setting-c', to: getRouterLink('setting') },
{ id: 'help', label: '帮助文档', icon: 'gt-file-c', href: 'https://gitcode.com/Gitcode-offical-team/GitCode-Docs/wiki' }
],
[
{ id: 'logout', label: '退出登录', icon: 'gt-exit', action: logout }
]
]);
</script>
<style lang="scss" scoped>
@import 'devui-theme/styles-var/devui-var.scss';
.g-user {
&-avatar {
height: 35px;
width: 35px;
border-radius: 50%;
cursor: pointer;
margin-left: 2px;
}
&-drawer {
padding: 24px;
&-info {
display: flex;
align-items: flex-start;
padding-bottom: 16px;
gap: 16px;
.g-user-avatar {
cursor: inherit;
}
&-list {
flex: 1;
&-name {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
font-weight: 500;
color: #2D2D2E;
line-height: 20px;
.close-icon {
cursor: pointer;
}
}
&-id {
font-size: 12px;
color: #707A87;
line-height: 16px;
}
}
}
&-block {
display: flex;
flex-direction: column;
padding: 16px 0;
& + & {
border-top: 1px solid var(--color-border-light);
}
:deep(.g-option-link) {
line-height: 36px;
font-size: 15px;
}
}
}
}
</style>
<style>
.g-user-drawer-container {
border-bottom-right-radius: 0 !important;
border-top-right-radius: 0 !important;
border-radius: 0 !important;
}
</style>

View File

@@ -0,0 +1,257 @@
<template>
<div class="full-w" :style="{ height: `${height}px` }">
<div class="g-header fixed z-20" ref="headerRef">
<div class="g-toolbar w-full overflow-hidden" :class="[searchClass, route?.meta?.headerClassName]">
<div class="g-toolbar-left flex-shrink items-center">
<div class="flex-center gap-[16px]">
<!-- <g-icon v-if="!asideSetShow" @click="$emit('clickMenu')" name="gt-hamburger" size="20px" class="cursor-pointer hover:!text-black" color="#1D1F3A"></g-icon>-->
<GLink class="g-toolbar-left-logo" href="/" />
<GLink class="g-toolbar-left-title" href="/">可信代码库</GLink>
<!-- <Entrance v-if="isLogin" :is-mobile="isMobile" /> -->
</div>
</div>
<div class="flex-1 flex items-center justify-end">
<Search class="g-toolbar-left-search" v-if="!isMobile" :key="$route.fullPath"/>
</div>
<div class="g-toolbar-right gap-[24px] flex-grow-0 flex-shrink-0">
<GLink
class="g-toolbar-right-link"
v-for="item in defaultLinkList"
:key="item.label"
:to="item.to"
:href="item.href"
:target="item.target"
>
{{ item.label }}
</GLink>
<template v-if="!isLogin">
<d-button class="border-none w-84 h-32 btn-add hidden-m" @click="handleLogin('login')" v-if="inSearch">
<GIcon name="gt-plane-add" size="16px" color="#9C9DB4" />
<span>新建</span>
</d-button>
<div class="cursor-pointer hidden-m" @click="handleLogin('login')" v-if="inSearch"><span>搜索记录</span></div>
<d-button variant="solid" color="primary" @click="handleLogin('login')">登录</d-button>
<d-button @click="handleLogin('register')" class="min-w-[64px]">注册</d-button>
</template>
<template v-else>
<!--ai copilot-->
<a class="g-header-copilot" href="/ai-copilot/">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.5 6C2.5 4.89543 3.39543 4 4.5 4H11.5C12.6046 4 13.5 4.89543 13.5 6V12C13.5 13.1046 12.6046 14 11.5 14H4.5C3.39543 14 2.5 13.1046 2.5 12V6ZM4.5 8C4.5 7.44772 4.94772 7 5.5 7C6.05228 7 6.5 7.44772 6.5 8V9C6.5 9.55228 6.05228 10 5.5 10C4.94772 10 4.5 9.55228 4.5 9V8ZM10.5 7C9.94772 7 9.5 7.44772 9.5 8V9C9.5 9.55228 9.94772 10 10.5 10C11.0523 10 11.5 9.55228 11.5 9V8C11.5 7.44772 11.0523 7 10.5 7Z" fill="white"/>
<path opacity="0.6" fill-rule="evenodd" clip-rule="evenodd" d="M8.75 3.29933C9.19835 3.03997 9.5 2.55521 9.5 2C9.5 1.17157 8.82843 0.5 8 0.5C7.17157 0.5 6.5 1.17157 6.5 2C6.5 2.55521 6.80165 3.03997 7.25 3.29933V5.5H8.75V3.29933ZM0 7C0 6.44772 0.447715 6 1 6H1.5V12H1C0.447715 12 0 11.5523 0 11V7ZM15 6H14.5V12H15C15.5523 12 16 11.5523 16 11V7C16 6.44772 15.5523 6 15 6Z" fill="white"/>
</svg>
<span class="ml-xs">AI Copilot</span>
</a>
<Create />
<SearchAiHistory />
<Notice />
<UserAvatar />
</template>
</div>
</div>
<div v-if="$slots.info || $slots.menu || $slots.bodyHeader" class="g-header-body">
<slot name="bodyHeader">
<div class="g-header-body-left h-full">
<slot name="info" />
</div>
<div class="g-header-body-right h-full">
<slot name="menu"></slot>
</div>
</slot>
</div>
<div v-if="$slots.headerBottom">
<slot name="headerBottom"></slot>
</div>
</div>
</div>
</template>
<script setup>
import { onBeforeUnmount, ref, watchEffect, computed } from 'vue';
import { useElementSize } from '@vueuse/core';
import { storeToRefs } from 'pinia';
import { useRoute } from 'vue-router';
import { useAccountStore } from '@/stores/user';
import { emitEvent } from '@/utils/eventBus';
import Create from './Create.vue';
import Entrance from './Entrance.vue';
import Notice from './Notice.vue';
import Search from './Search.vue';
import SearchAiHistory from './searchAiHistory.vue';
import UserAvatar from './UserAvatar.vue';
import { useWindowScroll } from '@vueuse/core';
import { usePageResize } from '@/utils/hooks/usePageResize';
import microApp from '@micro-zoe/micro-app';
const { y } = useWindowScroll();
const defaultLinkList = ref([]);
const route = useRoute();
const inSearch = ref(false);
const searchClass = computed(() => ({ 'border-b-transparent': inSearch.value && y.value === 0 }));
// header fixed布局高度自适应
const headerRef = ref(null);
const { height = 56 } = useElementSize(headerRef);
const userInfo = useAccountStore();
const { isLogin } = storeToRefs(userInfo);
const showSearch = computed(() => route.name !== 'home' && route.name !== 'search');
const handleLogin = (type) => {
emitEvent('login', { type, triggerType: '主动注册' });
};
const { isMobile } = usePageResize();
/* store 用户信息 */
/* @test 后面将清除 */
watchEffect(() => {
});
microApp.router.beforeEach({
'micoro-app-homeweb-app': (to, from) => {
if (to.pathname === '/search') {
inSearch.value = true;
} else {
inSearch.value = false;
}
}
});
defineExpose({
height
});
</script>
<style lang="scss" scoped>
@import 'devui-theme/styles-var/devui-var.scss';
$radius: 4px;
$add-radius: 48px;
$header-bg: #fff;
.g-header {
// overflow: hidden;
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: space-between;
background-color: $header-bg;
:deep(.g-link) {
color: $devui-text-weak;
}
.g-header-copilot {
background: rgba(41, 81, 224, 1);
color: #FFF;
width: 120px;
border-radius: 48px;
height: 32px;
display: flex;
align-items: center;
cursor: pointer;
justify-content: center;
&:hover {
background: rgba(41, 81, 224, .8);
}
}
.g-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
gap: 16px;
flex-basis: 56px;
overflow: hidden;
padding: 0 32px;
border-bottom: 1px solid #F1F1F8;
:deep(.devui-button) {
padding: 6px 16px;
border-radius: $add-radius;
}
&-left {
display: flex;
align-items: center;
gap: 25px;
&-logo {
display: inline-block;
width: 28px;
height: 28px;
background: url(../../assets/imgs/logo/logo.png) no-repeat;
background-size: contain;
vertical-align: middle;
background-position: 0 center;
}
&-title {
font-size: 1rem;
font-weight: 600;
}
&-search {
max-width: 340px;
border-radius: $radius;
}
}
&-right {
display: flex;
align-items: center;
@media screen and (max-width: 576px) {
@apply gap-[16px];
}
}
&.border-b-transparent {
border-bottom-color: transparent !important;
}
&.border-b-show {
border-bottom-color: #F1F1F8 !important;
}
}
.g-header-gray {
@apply bg-[#F9F9FB];
}
&-body {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 32px;
background-color: $header-bg;
border-bottom: 1px solid $devui-dividing-line;
flex-basis: 48px;
}
}
@media screen and (max-width: 1000px) {
.g-header {
.g-toolbar {
padding: 20px;
border-color: transparent;
}
.g-toolbar-left-logo {
width: 28px;
background-size: cover;
}
&-body {
padding: 0;
}
}
}
.btn-add {
background-color: #000;
color: #fff !important;
width: 84px;
font-size: 14px;
line-height: 20px;
span {
margin-left: 8px;
}
&:hover {
color: #fff;
background-color: #333;
}
}
@media screen and (max-width:576px) {
.hidden-m {
display: none;
}
.g-header-copilot {
display: none!important;;
}
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<div>
<div class="cursor-pointer" @click="triggerRec"><span>搜索记录</span></div>
<HistoryRecord ref="searchRec"/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import HistoryRecord from '@/components/historyRecord/index.vue';
const searchRec = ref(null);
const triggerRec = () => {
searchRec.value.open();
};
</script>
<style lang="scss" scoped>
</style>