搜索结果列表页面开发

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,78 @@
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
const className = 'w-full';
export default [
{
path: '/',
component: DefaultLayout,
meta: {
hasMobile: true,
loginHidden: true // 登录后不可进入
},
children: [
{
path: 'register',
name: 'register',
component: () => import('@/views/Mobile/Account/Register/index.vue'),
meta: {
title: '注册',
className
}
},
{
path: 'login',
name: 'login',
component: () => import('@/views/Mobile/Account/Login/Password/index.vue'),
meta: {
title: '登录',
className
}
},
{
path: 'loginByPhone',
name: 'loginByPhone',
component: () => import('@/views/Mobile/Account/Login/Phone/index.vue'),
meta: {
title: '短信登录',
className
}
},
// {
// path: 'bindPhone',
// name: 'bindPhone',
// component: () => import('@/views/Mobile/Account/BindPhone/index.vue'),
// meta: {
// title: '绑定手机号',
// className
// }
// },
{
path: 'resetPassword',
name: 'resetPassword',
component: () => import('@/views/Mobile/Account/ResetPassword/index.vue'),
meta: {
title: '重置密码',
className
}
}
]
},
{
path: '/oauth/callback',
name: 'oauth',
component: () => import('@/views/OAuth/callback.vue'),
meta: {
title: '第三方授权',
loginHidden: true
}
},
{
path: '/-/oauth/login',
name: 'oauthLogin',
component: () => import('@/views/OAuth/login.vue'),
meta: {
title: '登录账号'
}
}
];

View File

@@ -0,0 +1,142 @@
import DashboardLayout from '@/layouts/DashboardLayout/index.vue';
export default [
{
path: '/issues',
component: DashboardLayout,
meta: { needLogin: true },
children: [
{
path: '',
name: 'issues',
component: () => import('@/views/Dashboard/Issue/index.vue'),
meta: {
title: '我的issues',
reportTitle: '工作台-issue'
}
},
// {
// path: 'created',
// name: 'issuesCreated',
// component: () => import('@/views/Dashboard/Issue/index.vue'),
// meta: {
// basePath: 'issues',
// title: '我的issues-我创建的'
// }
// },
{
path: 'assigned',
name: 'issuesAssigned',
component: () => import('@/views/Dashboard/Issue/index.vue'),
meta: {
basePath: 'issues',
title: '我负责的issues'
}
},
{
path: 'about',
name: 'issuesMentioned',
component: () => import('@/views/Dashboard/Issue/index.vue'),
meta: {
basePath: 'issues',
title: '@我的issues'
}
}
]
},
{
path: '/contributed',
component: DashboardLayout,
meta: { needLogin: true },
children: [
{
path: '',
name: 'contributed',
component: () => import('@/views/Dashboard/Projects/index.vue'),
meta: {
basePath: 'contributed',
title: '参与的项目'
}
}
]
},
{
path: '/discussion',
component: DashboardLayout,
meta: { needLogin: true },
children: [
{
path: '',
name: 'discussionCreated',
component: () => import('@/views/Dashboard/Discussion/Created/index.vue'),
meta: {
basePath: 'discussionCreated',
title: '我创建的',
reportTitle: '工作台-讨论'
}
},
{
path: 'joined',
name: 'discussionJoined',
component: () => import('@/views/Dashboard/Discussion/Joined/index.vue'),
meta: {
basePath: 'discussionCreated',
title: '我参与的'
}
}
]
},
{
path: '/merge_requests',
component: DashboardLayout,
meta: { needLogin: true },
children: [
{
path: '',
name: 'merge',
component: () => import('@/views/Dashboard/Merge/index.vue'),
meta: {
basePath: 'merge',
title: '我的合并请求',
reportTitle: '工作台-mr'
}
},
// {
// path: 'created',
// name: 'mergeCreated',
// component: () => import('@/views/Dashboard/Merge/index.vue'),
// meta: {
// basePath: 'merge',
// title: '我的合并请求-我创建的'
// }
// },
{
path: 'assigned',
name: 'mergeAssigned',
component: () => import('@/views/Dashboard/Merge/index.vue'),
meta: {
basePath: 'merge',
title: '合并请求-我负责的'
}
},
{
path: 'mentioned',
name: 'mergeMentioned',
component: () => import('@/views/Dashboard/Merge/index.vue'),
meta: {
basePath: 'merge',
title: '合并请求-@我的'
}
},
{
path: 'reviewed',
name: 'mergeReviewed',
component: () => import('@/views/Dashboard/Merge/index.vue'),
meta: {
basePath: 'merge',
title: '合并请求-我评审的'
}
}
]
}
];

View File

@@ -0,0 +1,26 @@
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
export default [
{
path: '/online_editor',
meta: {
className: 'max-w-[100%] m-auto w-full',
},
component: DefaultLayout,
children: [
{
path: '',
name: 'onlineEditor',
component: () => import('@/views/Repo/online_editor/index.vue'),
meta: {
hasMobile: true,
asideMenu: 'drawer',
title: '在线编辑器',
reportTitle: '在线编辑器',
pType: 'onlineEditor',
hiddenFooter: true
}
}
]
}
]

View File

@@ -0,0 +1,52 @@
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
export default [
{
path: '/',
component: DefaultLayout,
children: [
{
path: '404',
name: '404',
component: () => import('@/views/ErrorPage/index.vue'),
meta: {
title: '404',
hasMobile: true
}
},
{
path: '403',
name: '403',
component: () => import('@/views/ErrorPage/index.vue'),
meta: {
title: '403',
hasMobile: true
}
},
{
path: '503',
name: '503',
component: () => import('@/views/ErrorPage/index.vue'),
meta: {
title: '503',
hasMobile: true
}
}
]
},
{
path: '/:pathMatch(.*)*',
component: DefaultLayout,
children: [
{
path: '',
name: 'notFound',
component: () => import('@/views/ErrorPage/index.vue'),
meta: {
title: '404',
hasMobile: true
}
}
]
}
];

57
src/router/config/home.ts Normal file
View File

@@ -0,0 +1,57 @@
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
export default [
{
path: '/:namespace+',
component: () => import('@/views/Homepage/index.vue'),
name: 'homepage',
meta: {
hasMobile: true, // 是否展示移动端
pageType: 'userOrOrg' // 用户或组织主页
}
},
{
path: '/',
component: DefaultLayout,
children: [
{
path: '',
name: 'home',
component: () => import('@/views/micro-page/proxy-homeweb.vue'),
meta: {
micorApp: ['micoro-app-homeweb-app'],
hasMobile: true,
hiddenFooter: true,
reportTitle: '首页',
className: 'w-full min-w-full',
headerClassName: ''
}
},
{
path: 'search',
name: 'search',
component: () => import('@/views/Jyh/Search/index.vue'),
meta: {
title: '高级搜索',
reportTitle: '搜索',
className: 'w-full min-w-full',
hiddenFooter: true,
hasMobile: true,
asideMenu: 'drawer' // 左侧菜单抽屉模式
}
},
{
path: 'explore',
name: 'explore',
component: () => import('@/views/micro-page/proxy-homeweb.vue'),
meta: {
micorApp: ['micoro-app-homeweb-app'],
title: '搜索开源',
className: 'w-full min-w-full',
hiddenFooter: true,
hasMobile: true
}
}
]
}
];

View File

@@ -0,0 +1,14 @@
export default [
{
path: 'issues/create/choose',
name: 'repoIssueCreateChoose',
component: () => import('@/views/Repo/Issue/Choose/index.vue'),
meta: {
title: '选择新建issue模版',
reportTitle: '选择新建issue模版',
// todo? 统计
pType: 'repoIssuesChoose',
needLogin: true
}
},
]

View File

@@ -0,0 +1,23 @@
import DefaultLayout from '@/layouts/DefaultLayout/index.vue';
// import NoticeLayout from '@/layouts/NoticeLayout/index.vue';
// import ProxyView from '@/views/UserCenter/proxy.vue';
export default [
{
path: '/notice',
component: DefaultLayout,
children: [
{
path: '',
name: 'notice',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '通知中心',
reportTitle: '通知',
needLogin: true
}
}
]
}
];

View File

@@ -0,0 +1,222 @@
import UserLayout from '@/layouts/UserLayout/index.vue';
// import ProxyView from '@/views/UserCenter/proxy.vue';
export default [
{
path: '/setting',
component: UserLayout,
meta: {
hideNav: true,
sidebarType: 'userSetting',
needLogin: true,
reportTitle: '用户设置'
},
children: [
{
path: '',
name: 'setting',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '用户设置',
application: 'user-center'
}
},
{
path: 'account',
name: 'settingAccount',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '用户设置',
application: 'user-center'
}
},
{
path: 'cla',
name: 'settingCla',
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '用户已签署CLA',
application: 'user-center'
}
},
{
path: 'preference',
name: 'settingPreference',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '偏好设置',
application: 'user-center'
}
},
{
path: 'notice',
name: 'settingNotice',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '用户设置',
application: 'user-center'
}
},
{
path: 'organization',
name: 'settingOrganization',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '组织管理',
application: 'user-center'
}
},
{
path: 'email',
name: 'settingEmail',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '电子邮件',
application: 'user-center'
}
},
// {
// path: 'password',
// name: 'settingPassword',
// component: () => import('@/views/UserCenter/proxy.vue'),
// meta: {
// title: '密码设置'
// }
// },
{
path: 'key-ssh',
name: 'settingSSH',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: 'SSH公钥',
application: 'user-center'
}
},
{
path: 'key-ssh/create',
name: 'settingSSHCreate',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '新建SSH',
application: 'user-center'
}
},
{
path: 'key-gpg',
name: 'settingGPG',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: 'GPG公钥',
application: 'user-center'
}
},
{
path: 'key-gpg/create',
name: 'settingGPGCreate',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '新建GPG',
application: 'user-center'
}
},
// {
// path: 'key-gpg',
// name: 'settingGPG',
// component: () => import('@/views/UserSetting/GPG/index.vue'),
// meta: {
// title: '用户设置'
// }
// },
// {
// path: 'key-gpg/create',
// name: 'settingGPGCreate',
// component: () => import('@/views/UserSetting/GPG/Create.vue'),
// meta: {
// title: '用户设置'
// }
// },
{
path: 'session',
name: 'settingSession',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '活动会话',
application: 'user-center'
}
},
{
path: 'repo',
name: 'settingRepo',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '项目管理',
application: 'user-center'
}
},
// {
// path: 'reply',
// name: 'settingReply',
// component: () => import('@/views/UserSetting/Reply/index.vue'),
// meta: {
// title: '用户设置-快捷回复'
// }
// },
// {
// path: 'reply/create',
// name: 'settingRepoCreate',
// component: () => import('@/views/UserSetting/Reply/Create/index.vue'),
// meta: {
// title: '用户设置'
// }
// },
// {
// path: 'logs',
// name: 'settingLog',
// component: () => import('@/views/UserSetting/Log/index.vue'),
// meta: {
// title: '用户设置-审计日志'
// }
// },
{
path: 'pipeline',
name: 'settingPipeline',
component: () => import('@/views/UserSetting/Pipeline/index.vue'),
meta: {
title: '流水线'
}
},
{
path: 'token-classic',
name: 'settingClassic',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '访问令牌(经典)',
application: 'user-center'
}
},
{
path: 'token-classic/create',
name: 'settingClassicCreate',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '新建访问令牌',
application: 'user-center'
}
}
]
}
];

75
src/router/config/user.ts Normal file
View File

@@ -0,0 +1,75 @@
import UserLayout from '@/layouts/UserLayout/index.vue';
// import ProxyView from '@/views/UserCenter/proxy.vue';
export default [
{
path: '/user/:namespace+',
component: UserLayout,
meta: { type: 'user', namespace: 'user' },
children: [
{
path: '',
name: 'user',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
pType: 'homepage',
hasMobile: true,
title: '个人主页',
reportTitle: '个人主页', // 用于数组上报分析
application: 'user-center'
}
},
{
path: 'repos',
name: 'userRepos',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '个人项目',
application: 'user-center'
}
},
{
path: 'stars',
name: 'userStars',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: 'Star列表',
application: 'user-center'
}
},
{
path: 'stars/:listName',
name: 'userStarsList',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: 'Star列表详情',
application: 'user-center'
}
},
{
path: 'fans',
name: 'userFans',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '粉丝',
application: 'user-center'
}
},
{
path: 'following',
name: 'userFollowing',
// component: ProxyView,
component: () => import('@/views/UserCenter/proxy.vue'),
meta: {
title: '关注的人',
application: 'user-center'
}
}
]
}
];

28
src/router/index.ts Normal file
View File

@@ -0,0 +1,28 @@
import { createRouter, createWebHistory } from 'vue-router';
import homeRouter from './config/home';
import settingRouter from './config/setting';
import userRouter from './config/user';
import dashboardRouter from './config/dashboard';
import noticeRouter from './config/notice';
import accountRouter from './config/account';
import errorRouter from './config/error';
import onLineEditor from './config/editor';
export { injectRouter } from './interceptor';
export const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
...onLineEditor,
...homeRouter,
...accountRouter,
...dashboardRouter,
...noticeRouter,
...settingRouter,
...userRouter,
...errorRouter
]
});
export default router;

235
src/router/interceptor.ts Normal file
View File

@@ -0,0 +1,235 @@
import { emitEvent } from '@/utils/eventBus';
import { repoPathSuffix } from '@/utils/regex';
import { useRepoId } from '@/utils/hooks/useRepoId';
import { useOrgId } from '@/utils/hooks/useOrgId';
import { usePageTitle } from '@/utils/hooks/useTitle';
import { repoInfoStore } from '@/stores/Repo';
import { orgInfoStore } from '@/stores/Org';
import { useAccountStore } from '@/stores/user';
import { getRepoRole, getRepo } from '@/api/repo';
import { getOrg, getPathType } from '@/api/org';
// import { globalFeaturedGroups } from '@/api/home';
import { reqCatch } from '@/utils/catch';
import { useReport } from '@/utils/hooks/useReport';
import { useGlobalInfoStore } from '@/stores/Global';
import { Message } from 'vue-devui';
// import cookie from 'js-cookie';
let currentOrgId = ''; // 当前组织路径
let currentRepoId = ''; // 当前项目路径
const visitedRepoList = 'visited_repo_list'; // 用于保存用户访问过哪些项目的代码
const setRepoVisited = (id: string): void => {
let visitedRepoIds = localStorage.getItem(visitedRepoList)?.split(',') || [];
visitedRepoIds.push(`${id}`);
visitedRepoIds = Array.from(new Set(visitedRepoIds));
localStorage.setItem(visitedRepoList, visitedRepoIds.join());
};
const isRepoVisited = (id: string): boolean => {
return localStorage.getItem(visitedRepoList)?.includes(id) || false;
};
/**
* 若uuid不存在则调用接口写入uuid
*/
// const checkUuid = () => {
// const uuid = cookie.get('uuid_tt_dd');
// if (!uuid) {
// globalFeaturedGroups(); // 这个接口并不是专门写入uuid的实际上任意一个接口都行
// }
// };
const fromHomeWeb = ['home', 'search', 'gStar', 'gStarApply', 'explore'];
export const injectRouter = router => {
const account = useAccountStore();
router.beforeEach(async (to, from) => {
// 如果是从blog跳转过来的判断是否登陆如果登陆则写入storage
// await account.checkIsLogin();
// 判断命名空间类型
const globalStore = useGlobalInfoStore();
globalStore.setNamespaceType(-1); // 重置namespaceType防止非组织路由请求组织信息
if (to.meta.pageType === 'userOrOrg') {
const { namespace } = to.params;
const params = {
path_name: Array.isArray(namespace) ? namespace.join('/') : namespace
};
let res = await reqCatch(getPathType, params);
if (!res.error) {
res = res?.data?.data;
if (res?.type === null) {
return { name: '404' };
}
const { type } = res; // 0-用户 1-组织
const typeConfig = {
0: 'user',
1: 'org'
};
globalStore.setMenuType(typeConfig[type], namespace);
globalStore.setNamespaceType(type);
}
}
// 上报ref设置
const BASE_URL = (import.meta as any).env.VITE_HOST;
const beforeRef = sessionStorage.getItem('ref');
const ref = beforeRef || document.referrer || '';
if (!fromHomeWeb.includes(to.name)) { // 设置全局ref
window.page_ref = ref;
sessionStorage.setItem('ref', BASE_URL + to.fullPath);
}
// 获取组织详情
if (to.meta.type === 'org' || globalStore.namespaceType === 1) { // 组织相关页面 || 组织主页
const { orgId } = useOrgId('%2F', to.params.namespace);
if (orgId.value !== currentOrgId) { // 第一次进入或切换组织时调用接口
const { setOrgInfo, setAccessLevel, setVisibility } = orgInfoStore();
const res = await reqCatch(getOrg, { orgId: orgId.value, with_full_path: true });
if (!res.error) {
setOrgInfo(res?.data?.data);
}
if (res?.error?.error_code === 404) {
return { name: '404' };
}
if (res?.error?.error_code === 403) { // 403代表无私有组织权限
setAccessLevel(0);
setVisibility('private');
return { name: '403' };
}
}
currentOrgId = orgId.value;
const { orgInfo } = orgInfoStore();
usePageTitle(String(to.meta.title || ''), orgInfo?.name);
}
// 获取项目详情和项目权限
if (to.meta.type === 'repo') {
const repoStore = repoInfoStore();
const { repoId } = useRepoId('%2F', to.params.namespace, to.params.repoName);
// 禁止访问以某些结尾的仓库wiki仓
if (!repoPathSuffix.test(repoId.value)) {
return { name: '404' };
}
if (repoId.value !== currentRepoId) {
// 0. 第一次进入或切换项目时调用接口
const promises = [getRepo({ repoId: repoId.value, statistics: true }, { customError: true })];
account.isLogin && promises.push(reqCatch(getRepoRole, { repoId: repoId.value }));
const [repoRes, roleRes] = await Promise.all(promises);
// 1. repo信息处理
const { setRepoInfo, setAccessLevel, setVisibility } = repoStore;
if (!repoRes.error) {
const { data } = repoRes.data;
setRepoInfo(data);
// 访问未开启模块显示404
const modules = data.module_setting.modules;
const moduleRouteConfig = new Map([
['WIKI', 'repoWiki'],
['ISSUE', 'repoIssues'],
['MERGE_REQUEST', 'repoMerge'],
['FORK', 'repoFork'],
['ANALYSIS', 'repoAnalysis'],
['DISCUSSION', 'repoDiscussion'],
['COMMUNITY', 'reqCommunity']
]);
modules.forEach(item => {
if ((to.name === moduleRouteConfig.get(item.key)) && item.value === '0') {
globalStore.setIsNotFound(true);
}
});
} else {
const errorCode = repoRes?.error?.error_code;
// 项目不存在
if (errorCode === 404) {
return { name: '404' };
}
// 403代表无私有项目权限
if (errorCode === 403) {
setAccessLevel(0);
setVisibility('private');
return { name: '403' };
}
Message.error(repoRes?.error?.error_message);
}
// 2. role信息处理
if (account.isLogin && !roleRes.error) {
setAccessLevel(roleRes.data.data?.access_level);
}
// 3. 更新currentRepoId
currentRepoId = repoId.value;
}
usePageTitle(String(to.meta.title || ''), repoStore?.repoInfo?.name);
if (to.name === 'repo') {
const repoId = repoStore.repoInfo?.id;
// 是否跳转项目dashboard
let isGotoRepoDashboard = false;
// 非visitor权限首次进入repoDashboard后续进入代码
if (!account.isLogin || !repoStore.isVisitor) {
isGotoRepoDashboard = !isRepoVisited(repoId);
}
// 当前路由配置默认进入代码tab未登录、非个人项目或非组织开发者成员项目进入首页导入失败项目进入代码页
if (isGotoRepoDashboard && repoStore.repoInfo.import_status !== 'failed') {
// 标记当前的repoId已访问
setRepoVisited(repoId);
// ref保存为跳转之前的
sessionStorage.setItem('ref', ref);
// 跳转repoDashboard
router.replace({
...to,
name: 'repoDashboard'
});
return false;
}
}
}
if (to.meta.type !== 'repo' && to.meta.type !== 'org') {
usePageTitle(String(to.meta.title || ''));
}
// 验证登录权限
if (to.meta.needLogin) { // 是否需要登录
if (account.isLogin) { // 已登录
return true;
} else {
emitEvent('login', { triggerType: to.meta?.loginType, Authorization: Boolean(to?.meta?.Authorization) });
return false;
}
} else {
if (account.isLogin && to.meta.loginHidden) { // 已登录,不能访问某些页面,进行跳转处理(如:登录注册)
const toOauth = to.name === 'oauth' && /(.*)_(.*)/.exec(to.query?.state); // 三方授权不跳转
const fromAtomGit = to.query.installation_id && /atomgit.com/.test(document.referrer); // 来自AtomGit应用的跳转
if (toOauth || fromAtomGit) return true;
return { name: 'dashboard' };
}
}
return true;
});
router.afterEach((to, from) => {
// 上报PV
if (to.fullPath === '/' || to.fullPath !== from.fullPath) { // 同地址切换不上报
if (!(to.name === 'repo' || to.name === 'repoDir') || (from.fullPath === '/' && to.name === 'repoDir')) {
// 子应用单独处理
if (fromHomeWeb.includes(to.name)) return;
// 项目中代码点击tab单独上报从其他地方跳转除外
useReport('pageview', {});
}
}
});
};