搜索结果列表页面开发

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

119
src/stores/Global/index.ts Normal file
View File

@@ -0,0 +1,119 @@
import { ref, computed } from 'vue';
import { defineStore } from 'pinia';
import menu from '@/constant/menu';
import { type listType, type itemProps } from '@/components/NavTabs/types';
import { useRoute, useRouter } from 'vue-router';
import { getCustomMenuList } from '@/api/org/devIndex';
export const useGlobalInfoStore = defineStore('globalInfo', () => {
const route = useRoute();
const globalMenuInfo = ref<listType>(menu); // 全部菜单信息
const menuType = ref(route?.meta?.type || 'repo'); // 当前菜单类型
const customMenuInfo = ref([]); // org自定义菜单
const isNotFound = ref(false); // 是否展示404页面
const showTools = ref(true); // 是否展示工具栏
const namespaceType = ref(-1); // 0 - 用户 1 - 组织
const globalTheme = ref('light'); // 网站主题
const menuInfo = computed(() => {
// 当前菜单
return globalMenuInfo.value[menuType.value] || [];
});
// 设置单类菜单信息
const setNamespaceType = (val: number) => {
namespaceType.value = val;
};
// 设置全部菜单信息
const setGlobalMenuInfo = (val: listType) => {
globalMenuInfo.value = {
...globalMenuInfo.value,
...val
};
};
// 设置单类菜单信息
const setMenuInfo = (val: itemProps) => {
globalMenuInfo.value[menuType.value] = val;
};
/**
* 设置菜单类型
* @param val 类型
* @param routeNamespace 路由中的命名空间(某些地方获取不到,需要手动传入)
*/
const setMenuType = async(val: string, routeNamespace?: string) => {
if (val) {
menuType.value = val;
// 组织 需要加载自定义菜单
if (val === 'org') {
const namespace = routeNamespace || route.params.namespace;
const res = await getCustomMenuList({
namespace: Array.isArray(namespace) ? namespace.join('/') : namespace
});
customMenuInfo.value = (res?.data?.data || []).map((item) => ({
...item,
id: item.id || item.name,
label: item.name,
icon: item.link || 'gt-link',
link: item.content
}));
}
}
};
/**
* 设置菜单标题旁的数字mr数量成员数量
* @param id 菜单id
* @param val 增加或减少的数量
* @param actionType 类型 add增加 reduce减少 equal等于
*/
const updateMenuNum = (id: string, val: number, actionType: 'add' | 'reduce' | 'equal' = 'equal') => {
const list = [...(globalMenuInfo.value[menuType.value] || []), ...customMenuInfo.value];
const item = list.find((item) => item.id === id) || {};
if (actionType === 'equal') {
item.num = val;
}
if (actionType === 'add') {
item.num = item.num ? item.num + val : val; // 新增前判断num是否存在
}
if (actionType === 'reduce') {
item.num = item.num <= 0 ? 0 : item.num - val; // 防止刷成负数
}
};
const setIsNotFound = (val: boolean) => {
isNotFound.value = val;
};
const setShowTools = (val: boolean) => {
showTools.value = val;
};
/**
* TODO: 设置主题模仿github用于设置仓库项目markdown图片
* 前端暂时固定单一主题,后续可能会提供设置功能
*/
const setGlobalTheme = (val: string) => {
globalTheme.value = val;
};
return {
customMenuInfo,
menuInfo,
menuType,
isNotFound,
showTools,
namespaceType,
globalTheme,
setMenuType,
updateMenuNum,
setMenuInfo,
setGlobalMenuInfo,
setIsNotFound,
setShowTools,
setNamespaceType,
setGlobalTheme
};
});

183
src/stores/Org/index.ts Normal file
View File

@@ -0,0 +1,183 @@
import { acceptHMRUpdate, defineStore } from 'pinia';
import { reactive, ref, computed } from 'vue';
import setting from '@/setting';
export interface OrgInfo {
avatar?: string
name?: string
description?: string
path?: string;
email?: string
location?: string
orgId?: string
repoId?: string
visibility?: string
web_url?: string
module_setting?: {
group_id: string,
modules: {
key: string,
type: string,
value: string
}[]
}
my_role?: {
access_level: number,
}
[proName: string]: any
}
export interface memObj {
name: string, username: string, iam_id: string, avatar_url: string, [p: string]: any
}
const { visitor, developer, admin } = setting.role;
export const orgInfoStore = defineStore('globalOrgInfo', () => {
const orgInfo = ref<OrgInfo>({});
const isFollow = ref<boolean>(false);
const orgNameSpace = ref<string>('');
const isCommunity = ref<boolean>(false);
const communityUrl = ref<string>('');
const communityactivityUrl = ref<string>('');
const communityInfo = ref<Record<string, any>>({});
const memList = ref<memObj[]>([]);
const memCount = ref(0);
const orgDevArticle = ref<Record<string, any>>({});
const isDevLogin = ref<boolean>(false);
const advertisementData = ref<any[]>([]);
const articleTotal = ref<number>(0);
const fansTotal = ref<number>(0);
const access_level = computed(() => {
return orgInfo.value?.my_role?.access_level || 0;
});
const group_quota = localStorage.getItem('group_quota') || 1;
const manageable_group_num = localStorage.getItem('manageable_group_num') || 0;
const isCreateOrg = ref<boolean>(parseInt(group_quota as any) - parseInt(manageable_group_num as any) > 0);
const isPrivate = computed(() => { // 是否私有
return orgInfo.value?.visibility === 'private';
});
const isAdmin = computed(() => {
return access_level?.value >= admin;
});
const isDeveloper = computed(() => {
return access_level?.value >= developer;
});
const isVisitor = computed(() => {
return access_level?.value >= visitor;
});
const setOrgInfo = (info?: OrgInfo) => {
if (info) {
info['email'] = info.group_extend.email;
info['home_page'] = info.group_extend.home_page;
info['location'] = info.group_extend.location;
info['avatar_url1'] = info.avatar;
// isFollow.value = !!info?.starred;
orgInfo.value = {
...orgInfo.value,
...info
};
}
};
const setAccessLevel = (val: number) => {
orgInfo.value = {
...orgInfo.value,
my_role: {
access_level: val
}
};
};
const setVisibility = (val: string) => {
orgInfo.value = {
...orgInfo.value,
visibility: val
};
};
const setFollow = (val?: boolean) => {
isFollow.value = !!val;
};
const setNameSpace = (orgId: string) => {
orgNameSpace.value = orgId;
};
const setCommunity = (val: boolean) => {
isCommunity.value = val;
};
const setCommunityUrl = (param: { communityUrl: string, communityactivityUrl: string }) => {
communityUrl.value = param.communityUrl;
communityactivityUrl.value = param.communityactivityUrl;
};
const setCommunityInfo = (obj:Record<string, any>) => {
communityInfo.value = obj;
};
const setOrgMember = (num: number, list: memObj[]) => {
memCount.value = num;
memList.value = list;
};
const setDevLogin = (val:boolean) => {
isDevLogin.value = val;
};
const setAdvertisementData = (val:any[]) => {
advertisementData.value = val;
};
const setArticleTotal = (num:number) => {
articleTotal.value = num;
};
const setFansTotal = (num:number) => {
fansTotal.value = num;
};
const setIsCreateOrg = (val:boolean) => {
isCreateOrg.value = val;
};
return {
isPrivate,
isAdmin,
isDeveloper,
isVisitor,
access_level,
orgInfo,
isFollow,
orgNameSpace,
isCommunity,
communityUrl,
communityInfo,
communityactivityUrl,
memList,
memCount,
orgDevArticle,
advertisementData,
isDevLogin,
articleTotal,
fansTotal,
isCreateOrg,
setOrgInfo,
setAccessLevel,
setVisibility,
setFollow,
setNameSpace,
setCommunity,
setCommunityUrl,
setCommunityInfo,
setOrgMember,
setAdvertisementData,
setDevLogin,
setArticleTotal,
setFansTotal,
setIsCreateOrg
};
});
if ((import.meta as any).hot) {
(import.meta as any).hot.accept(acceptHMRUpdate(orgInfoStore, (import.meta as any).hot));
};

View File

@@ -0,0 +1,31 @@
import { defineStore } from 'pinia';
import { reactive } from 'vue';
import { fetchPrExaminePerson } from '@/api/merge';
import keyBy from 'lodash/keyBy';
import type { IAuthor } from '@/api/issue/types.ts';
export const useExamineData = defineStore('repoExamineData', () => {
const examineData = reactive<{ reviewers: IAuthor[], approvers: IAuthor[], testers: IAuthor[]; }>({
reviewers: [],
approvers: [],
testers: []
});
const fetchExamineData = async(data: { repoId: string, prId: string; }) => {
const res = await fetchPrExaminePerson({ repoId: data.repoId, iid: data.prId });
if (!res.error) {
const data: { [type: 'reviewer' | 'approver' | 'tester' | string]: { persons: IAuthor[]; }; } = keyBy(
res.data || [],
'type'
);
examineData.reviewers = data.reviewer.persons || [];
examineData.approvers = data.approver.persons || [];
examineData.testers = data.tester.persons || [];
}
};
return {
examineData,
fetchExamineData
};
});

131
src/stores/Repo/index.ts Normal file
View File

@@ -0,0 +1,131 @@
import { ref, reactive, computed } from 'vue';
import { defineStore } from 'pinia';
import { getRepo } from '@/api/repo';
import getRepoId from '@/utils/getRepoId';
import setting from '@/setting';
export interface repoInfoType {
id?: number;
name?:string;
path?:string;
path_with_namespace?:string;
created_at?:string;
star_count?:number;
starred?:boolean;
creator?:{
id:number;
iam_id:string;
username:string;
nick_name:string;
};
description?:string;
module_setting?:{
repo_id:string;
modules:{
key: string,
type: string,
value: string
}[]
};
web_url?:string;
[propName: string]: any;
}
const { visitor, developer, admin } = setting.role;
export const repoInfoStore = defineStore('globalRepoInfo', () => {
const repoInfo = ref<repoInfoType>({});
const access_level = ref<number>(0);
const setAccessLevel = (val: number) => {
access_level.value = val;
};
const setVisibility = (val: string) => {
repoInfo.value = {
...repoInfo.value,
visibility: val
};
};
const isPrivate = computed(() => { // 是否私有
return repoInfo.value?.visibility === 'private';
});
const isArchived = computed(() => { // 是否归档
return repoInfo.value?.archived;
});
const isAdminOperate = computed(() => { // 操作权限(管理者且未归档,为了兼容未考虑归档时的权限)
return access_level?.value >= admin && !repoInfo.value?.archived;
});
const isDeveloperOperate = computed(() => { // 操作权限(开发者且未归档,为了兼容未考虑归档时的权限)
return access_level?.value >= developer && !repoInfo.value?.archived;
});
const isVisitorOperate = computed(() => { // 操作权限(浏览者且未归档,为了兼容未考虑归档时的权限)
return access_level?.value >= visitor && !repoInfo.value?.archived;
});
const isAdmin = computed(() => {
return access_level?.value >= admin;
});
const isDeveloper = computed(() => {
return access_level?.value >= developer;
});
const isVisitor = computed(() => {
return access_level?.value >= visitor;
});
const setRepoInfo = (data: repoInfoType) => {
repoInfo.value = {
...repoInfo.value,
...data
};
};
const setReleaseCount = (count: number) => {
repoInfo.value.release_count = count;
};
const setTagsCount = (count: number) => {
repoInfo.value.tag_count = count;
};
const setForkVisible = (visible: boolean)=> {
repoInfo.value.module_setting?.modules.forEach((item)=> {
if (item.key === 'FORK' && item.type === 'PROJECT_MODULE') {
item.value = visible ? '1' : '0'
}
})
}
const getRepoInfo = async(namePath?: string) => {
const res = await getRepo({ repoId: namePath || getRepoId(), statistics: true });
if (!res.error) {
const { data } = res.data;
setRepoInfo(data);
return data;
}
return null;
};
return {
isPrivate,
isAdmin,
isDeveloper,
isVisitor,
isArchived,
isAdminOperate,
isDeveloperOperate,
isVisitorOperate,
access_level,
repoInfo,
setRepoInfo,
setVisibility,
setAccessLevel,
setReleaseCount,
setTagsCount,
setForkVisible,
getRepoInfo
};
});

View File

@@ -0,0 +1,12 @@
import { defineStore } from 'pinia';
export const useRepoMemberStore = defineStore('repoMemberState',{
state:()=>({
}),
getters:{},
actions:{
getOrgList(){//项目成员列表
}
}
});

View File

@@ -0,0 +1,16 @@
import { defineStore } from 'pinia';
import type { OrgItem } from '@/stores/UserSetting/type';
interface OrgState{
orgList:Array<OrgItem>;
}
export const useOrganizationStore = defineStore('organizationState', {
state: ():OrgState => ({
orgList: []// 组织列表
}),
getters: {},
actions: {
getOrgList() { // 获取个人的组织列表
return [];
}
}
});

View File

@@ -0,0 +1,11 @@
export interface OrgItem{//组织
id: string;// id
name: string;//名字
namespace?: string;
avatar: string; // 个人头像
description?: string;//描述
memberCount?: number;//成员
type:String;//类型
role:String|Number;//角色
[key:string]:any;
}

24
src/stores/merge.ts Normal file
View File

@@ -0,0 +1,24 @@
import { ref, computed } from 'vue';
import { getStore, setStore } from '@/utils/storage';
import { defineStore } from 'pinia';
import { localStorageKeys } from '@/constant/index';
// mr 详情页 文件变更
export const useMrChangeStore = defineStore('merge-change-db', () => {
// 记录正在添加 issue 的 diff 文件: `文件名-行号-左/右`
const addingDiscussionsFile = ref('');
// 记录 diff 文件是否折叠 issue
const foldDiscussionsFile = ref({ curClickFile: '', record: {} });
// 显示设置
const setting = getStore(localStorageKeys.merge_showformat_setting);
const mergeDiffOutputFormat = ref(setting ? setting.mergeDiffOutputFormat : 'side-by-side');
const ignore_whitespace_change = ref(setting ? setting.ignore_whitespace_change : false);
return {
addingDiscussionsFile,
foldDiscussionsFile,
mergeDiffOutputFormat,
ignore_whitespace_change
};
});

93
src/stores/ui/resize.ts Normal file
View File

@@ -0,0 +1,93 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
import { useThrottleFn } from '@vueuse/core';
import mitt, { EventType } from 'mitt';
interface Events extends Record<EventType, unknown> {
resize: [number, number];
}
class WindowResize {
private active = false;
isActive() {
return this.active;
}
start() {
if (this.active) {
this.stop();
}
window.addEventListener('resize', this.onResizeDebounced);
this.active = true;
}
stop() {
window.removeEventListener('resize', this.onResizeDebounced);
this.active = false;
}
/**
* 初次进入页面且已通过 onWindowResize 注册后,需手动触发一遍
*/
onResize() {
// [window.innerWidth, window.innerHeight]
const size: [number, number] = [
document.documentElement.clientWidth,
document.documentElement.clientHeight
];
emitter.emit('resize', size);
}
private onResizeDebounced = useThrottleFn(this.onResize, 100);
}
/**
* 注册响应窗口大小变化的回调函数,自动启停优化性能
* @example
* // vue steup 里面使用
* onBeforeUnmount(
* onWindowResize((size) => console.log('屏幕尺寸', size))
* )
*/
const onWindowResize = (cb: (size: [number, number]) => void) => {
if (!windowResizeObserver.isActive()) {
windowResizeObserver.start();
}
emitter.on('resize', cb);
return () => {
emitter.off('resize', cb);
if (emitter.all.size === 0) {
windowResizeObserver.stop();
}
};
};
const emitter = mitt<Events>();
const windowResizeObserver = new WindowResize();
export const useResizeStore = defineStore('windowResize', () => {
const MOBILE_WIDTH_LIMIT = 1200;
const isMobile = ref(false);
const isMobileDevice = (size: [number, number]) => {
const [width] = size;
return width <= MOBILE_WIDTH_LIMIT;
};
// 全局可用
onWindowResize((size) => {
isMobile.value = isMobileDevice(size);
});
windowResizeObserver.onResize();
return {
isMobile,
onWindowResize,
isMobileDevice,
windowResizeObserver
};
});

143
src/stores/user.ts Normal file
View File

@@ -0,0 +1,143 @@
import { acceptHMRUpdate, defineStore } from 'pinia';
import { reactive, ref } from 'vue';
import { getUserToken, getUserInfo } from '@/api/user';
export interface AccountInfo {
domain_id?: string;
email?: string;
id?: string;
arts_id?: string;
mobile?: string;
avatar?: string;
nickname?: string;
username?: string;
access_token?: string;
refresh_token?: string;
xauth_token?: string;
isFollow?: boolean;
atomgit_username?: string;
[x: string]: any;
}
export const useAccountStore = defineStore('accountInfo', () => {
const cacheInfo = localStorage.getItem('userInfo');
const isLogin = ref(Boolean(localStorage.getItem('access_token')));
const userInfo = cacheInfo ? JSON.parse(cacheInfo) : {};
const accountInfo = reactive<AccountInfo>({
access_token: localStorage.getItem('access_token') || '',
refresh_token: localStorage.getItem('refresh_token') || '',
xauth_token: localStorage.getItem('xauth_token') || '',
...userInfo
});
const refresh = () => {
/** 重定向跳转时强刷使用 */
const info = localStorage.getItem('userInfo');
isLogin.value = Boolean(localStorage.getItem('access_token'));
const userData = info ? JSON.parse(info) : {};
for (const key in userData) {
accountInfo[key] = userData[key];
}
accountInfo.access_token = localStorage.getItem('access_token') || '';
accountInfo.refresh_token = localStorage.getItem('refresh_token') || '';
accountInfo.xauth_token = localStorage.getItem('xauth_token') || '';
};
const saveStatus = (status: boolean) => {
isLogin.value = status;
};
const saveAccountInfo = (info?: AccountInfo) => {
if (info) {
for (const key in info) {
accountInfo[key] = info[key];
}
} else {
for (const key in accountInfo) {
accountInfo[key] = '';
}
}
};
// 可能有从博客跳转过来的,通过接口判断是否登陆
const checkIsLogin = async () => {
const { data, error } = await getUserToken();
if (data?.data) {
const { access_token, refresh_token } = data.data;
localStorage.setItem('access_token', access_token);
localStorage.setItem('refresh_token', refresh_token);
saveStatus(true);
const userRes = await getUserInfo();
if (userRes.data?.data) {
localStorage.setItem('userInfo', JSON.stringify(userRes.data?.data));
saveAccountInfo({
access_token: localStorage.getItem('access_token') || '',
refresh_token: localStorage.getItem('refresh_token') || '',
...userRes.data?.data
});
}
return true;
}
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('userInfo');
saveAccountInfo({});
return false;
};
const saveAtomgitUserName = (username: string) => {
accountInfo.atomgit_username = username;
};
return {
isLogin,
accountInfo,
checkIsLogin,
saveStatus,
refresh,
saveAccountInfo,
saveAtomgitUserName
};
});
export const otherAccountStore = defineStore('otherInfo', () => {
const isLogin = ref(Boolean(localStorage.getItem('access_token')));
const userInfo = {};
const accountInfo = reactive<AccountInfo>({
...userInfo
});
const saveAccountInfo = (info?: AccountInfo) => {
if (info) {
for (const key in info) {
accountInfo[key] = info[key];
}
} else {
for (const key in accountInfo) {
accountInfo[key] = '';
}
}
};
const saveFollowed = (isFollowed?: boolean) => {
accountInfo.isFollow = isFollowed;
};
return {
isLogin,
accountInfo,
saveAccountInfo,
saveFollowed
};
});
export const entranceData = defineStore('entrance', () => {
const repoList = ref([]);
const orgList = ref([]);
return {
repoList,
orgList
};
});
if ((import.meta as any).hot) {
(import.meta as any).hot.accept(acceptHMRUpdate(useAccountStore, (import.meta as any).hot));
}