搜索结果列表页面开发

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,442 @@
<template>
<div>
<div v-if="!diffs" class="p-5"><d-skeleton></d-skeleton></div>
<d-splitter v-if="diffs" class="splitter-border" style="height: 100%">
<template v-slot:DSplitterPane>
<d-splitter-pane
collapseDirection="before"
size="320px"
minSize="320px"
:collapsible="true"
@size-change="setSplitterCollapse"
@collapsed-change="collapsedChange"
>
<div class="pane-content">
<div v-if="diffTree" style="min-width: 320px" class="left-tree pl-5 pr-5 pt-5">
<d-search
class="mb10"
style="width: 100%"
is-keyup-search
placeholder="查找文件"
:delay="100"
@search="onSearch"
></d-search>
<d-tree @node-click="onTreeClick" ref="treeRef" :data="diffTree" class="mt-2">
<template #content="{ nodeData }">
<div class="ml-2" :title="nodeData?.file_path">{{ nodeData?.file_path }}</div>
</template>
<template #icon="{ nodeData }">
<span class="inline-block w-5">
<Icon v-if="nodeData.isLeaf" name="gt-file-c" />
<Icon v-else :name="nodeData.expanded ? 'gt-folder-open-c' : 'gt-folder-c'" />
</span>
</template>
</d-tree>
</div>
</div>
</d-splitter-pane>
<d-splitter-pane minSize="15%">
<div class="pane-content">
<div class="flex-1 pt-5 bg-[#f6f6f8]">
<div class="flex px-5 justify-between items-center">
<div class="flex items-center">
<StatusSelect v-model:value="selected" />
<div class="flex items-center ml-4">
<div class="text-[#2D2D2E] flex items-center">
<div class="text-[#517ca0] mx-1">{{ diffs.changes?.length }}</div>
个文件变更
</div>
<p class="text-green-500 ml-4">+{{ diffs.added_lines }}</p>
<p class="text-red-500 ml-1">-{{ diffs.removed_lines }}</p>
</div>
<span class="w-px h-5 bg-gray-300 mx-4"></span>
<div class="flex items-center gap-2">
<d-dropdown style="width: 150px" :position="['bottom-start']" align="start" close-scope="all">
<div class="flex items-center justify-between gap-2 cursor-pointer">
<div class="commitTitle text-[#707a87] hover:text-[#2d2d2e]">
{{ preCommitName }}
</div>
<Icon name="gt-select-arrow-down-12" size="12px"></Icon>
</div>
<template #menu>
<ul>
<li
class="p-2 cursor-pointer hover:bg-[#f6f6f8]"
v-for="item in firstCommitOptions"
:key="item.value"
:value="item.value"
:name="item.name"
@click="
preCommit = item.value;
baseLineChange();
"
>
<div class="px-2">
<div style="font-weight: 700">{{ item.name }}</div>
<div v-if="item.head_commit_sha">
{{ item.head_commit_sha.slice(0, 8) }}
</div>
<div v-if="item.created_at">{{ formatTime(item.created_at) }}</div>
</div>
</li>
</ul>
</template>
</d-dropdown>
<Icon name="gt-exit" size="12px"></Icon>
<d-dropdown style="width: 150px" :position="['bottom-start']" align="start">
<div class="flex items-center justify-between gap-2 cursor-pointer">
<div class="commitTitle text-[#707a87] hover:text-[#2d2d2e]">
{{ afterCommitName }}
</div>
<Icon name="gt-select-arrow-down-12" size="12px"></Icon>
</div>
<template #menu>
<ul>
<li
class="p-2 cursor-pointer hover:bg-[#f6f6f8]"
v-for="item in lastCommitOptions"
:key="item.value"
:value="item.value"
:name="item.name"
@click="
afterCommit = item.value;
baseLineChange();
"
>
<div class="px-2">
<div style="font-weight: 700">{{ item.name }}</div>
<div v-if="item.head_commit_sha">
{{ item.head_commit_sha.slice(0, 8) }}
</div>
<div v-if="item.created_at">{{ formatTime(item.created_at) }}</div>
</div>
</li>
</ul>
</template>
</d-dropdown>
</div>
<div class="ml-4"></div>
</div>
<div class="flex items-center">
<d-popover content="展开/收起全部文件" trigger="hover" :position="['top']">
<span class="flex items-center cursor-pointer" @click="handleExpand">
<Icon name="gt-frame-expand2"></Icon>
</span>
</d-popover>
<span class="w-px h-5 bg-gray-300 mx-3"></span>
<d-popover content="设置" trigger="hover" :position="['top']">
<ShowFormat />
</d-popover>
</div>
</div>
<div class="mt-4 flex">
<div v-if="diffsloading" class="p-5 h-80 w-[100%]"><d-skeleton></d-skeleton></div>
<DataPanel
class="filter-diff-list"
:loading="diffsloading"
:empty="!filterDiffs?.length"
:skeleton="diffsloading"
:card="false"
>
<div class="flex-1 codeblock p-5 pt-0" :key="`${preCommit}-${afterCommit}`" v-show="!diffsloading">
<div
:class="{ selectItem: selectedFile === item.file_path }"
class="mb-3 overflow-auto rounded-[8px]"
style="box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.09)"
v-for="(item, index) in filterDiffs"
:key="item.file_path"
@click="codelistClick(item.file_path)"
>
<DiffView
:index="index"
:repoId="repoId"
:diffObj="item"
:discussions="diss?.length ? diss.find((x) => x.path === item.file_path) : null"
:showIssue="true"
:allowComment="canComment"
:showViewSource="true"
:showReadBtn="true"
@onMergeAble="$emit('onMergeAble')"
@foldChange="(e) => (item.fold = e)"
/>
</div>
</div>
</DataPanel>
</div>
</div>
</div>
</d-splitter-pane>
</template>
</d-splitter>
<!-- 勿删 Status 枚举需要 -->
<div
class="hidden text-[#fa9841] bg-[#fa984133] bg-[#3ac29533] bg-[#f66f6a33] bg-[#71757f33] text-[#3ac295] text-[#f66f6a] text-[#71757f]"
></div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, toValue, watch, reactive, onMounted, provide, inject } from 'vue';
import { useReq } from '@/utils/hooks/useReq';
import { changes } from '@/api/merge/index';
import ShowFormat from './ShowFormat.vue';
import StatusSelect from './StatusSelect.vue';
import { changesTrees, versions, getDis } from '@/api/merge/index';
import DiffView from './DiffView.vue';
import { useAccountStore } from '@/stores/user';
import { escapeResData } from '@/utils';
import { diffFilter } from './support';
import { repoInfoStore } from '@/stores/Repo';
import { useMrChangeStore } from '@/stores/merge';
import { formatTime } from './support';
const mrChangeStore = useMrChangeStore();
const user = useAccountStore();
const accountInfo = user.accountInfo;
const arts_id = accountInfo?.arts_id;
const { isPrivate, isVisitor } = repoInfoStore();
const canComment = computed(() => {
if (isPrivate) return isVisitor;
else return !!arts_id;
});
//
const repoId = inject('repoId');
const iid = inject('iid');
provide('arts_id', arts_id);
provide('accountInfo', accountInfo);
const mrInfo = inject('mrInfo');
provide('source_branch', mrInfo?.value?.source_branch);
// 筛选
const selected = ref([]);
const keyWord = ref('');
const allFold = ref(false);
const handleExpand = () => {
diffs?.value?.changes?.forEach((x) => (x.collapsed = allFold.value));
allFold.value = !allFold.value;
};
// 基线
const preCommit = ref(null);
const afterCommit = ref(null);
provide('preCommit', preCommit);
provide('afterCommit', afterCommit);
const preCommitName = computed(() => {
return commitOptions?.value?.find((x) => x.value === preCommit.value)?.name || '';
});
const afterCommitName = computed(() => {
return commitOptions?.value?.find((x) => x.value === afterCommit.value)?.name || '';
});
const from_diff_id = computed(() => {
if (!commitOptions?.value?.length) return null;
if (preCommit.value === -1 && afterCommit.value === commitOptions.value[0].value) return null;
return afterCommit.value;
});
const to_diff_id = computed(() => {
if (preCommit.value === -1) return null;
return preCommit.value;
});
const { data: commitOptions } = useReq(versions, reactive({ repoId, iid }), null, (res) => {
const data = res.map((x, i) => ({ ...x, value: x.id }));
for (let i = 0; i < data.length; i++) {
if (i === 0) data[i].name = '最新版本';
else data[i].name = '版本' + (data.length - i);
}
return [...data, { value: -1, name: '基线' }];
});
watch(commitOptions, () => {
if (!commitOptions.value?.length) return;
//
preCommit.value = -1;
afterCommit.value = commitOptions.value[0].value;
});
const firstCommitOptions = computed(() => {
if (!commitOptions.value?.length) return [];
const firstOptions = commitOptions.value.filter((x) => x.value < afterCommit.value);
return [...firstOptions];
});
const lastCommitOptions = computed(() => {
if (!preCommit.value || !commitOptions.value?.length) return [];
const afterOptions = commitOptions.value.filter((x) => x.value > preCommit.value);
return [...afterOptions];
});
const baseLineDateNow = ref(+new Date());
const baseLineChange = () => {
loadDiffTree();
loadDiff();
baseLineDateNow.value = +new Date();
};
// diffs files tree
const formatTree = (childrenProp, pickProps) => (sourceList) => {
if (!Array.isArray(sourceList)) return sourceList;
return sourceList.map((node) => {
return {
...node,
children: formatTree(childrenProp, pickProps)(node[childrenProp]),
...pickProps(node)
};
});
};
const findTreeNode = (id, nodes) => {
for (const node of nodes) {
if (node.id === id) return node;
if (node.children?.length) {
const deepRes = findTreeNode(id, node.children);
if (deepRes) return deepRes;
}
}
return null;
};
const diffTree = ref(null);
const loadDiffTree = () => {
changesTrees(
reactive({
repoId,
iid,
view: 'simple',
from_diff_id,
to_diff_id
})
)
.then((res) => {
const data = escapeResData(res);
const pickProps = (node) => ({
label: node.title,
id: node.file_path,
expanded: true,
selected: false
});
diffTree.value = formatTree('items', pickProps)(data.tree);
})
.catch((e) => {});
};
//
const treeRef = ref();
const onSearch = (value) => {
treeRef.value.treeFactory.searchTree(value, { isFilter: true });
};
const selectedFile = ref('');
const onTreeClick = (node) => {
selectedFile.value = node?.file_path;
const thenode = diffs?.value?.changes?.find((x) => x.file_path === selectedFile.value);
if (thenode) thenode.fold = false;
if (node.childNodeCount > 0) {
node.expanded = !node.expanded;
}
//
};
const codelistClick = (file_path) => {
selectedFile.value = file_path;
//
const thenode = findTreeNode(file_path, diffTree.value);
//
treeRef.value.treeFactory.selectNode(thenode);
};
// diffs (文件)
const diffs = ref();
const diffsloading = ref(false);
const loadDiff = () => {
diffsloading.value = true;
const diffsParams = reactive({
repoId,
iid,
view: 'simple',
ignore_whitespace_change: mrChangeStore.ignore_whitespace_change,
from_diff_id,
to_diff_id
});
changes(diffsParams)
.then((res) => {
diffsloading.value = false;
const data = escapeResData(res);
diffs.value = data;
//
})
.catch((e) => {
diffsloading.value = false;
});
};
const filterDiffs = computed(() => {
if (!diffs?.value?.changes) return [];
return diffs.value.changes?.filter((m) => diffFilter(m, selected));
});
//
// diss
const { data: diss } = useReq(getDis, reactive({ repoId, iid }));
watch(
() => mrChangeStore.ignore_whitespace_change,
() => {
diffs.value = [];
loadDiff();
}
);
onMounted(() => {
loadDiffTree();
loadDiff();
});
const collapsedChange = (e) => {
if (e) setSplitterCollapse('4px');
else setSplitterCollapse('320px');
};
const setSplitterCollapse = (left) => {
setTimeout(() => {
document.querySelector('.devui-splitter__collapse').style.left = left;
}, 0);
};
</script>
<style scoped lang="scss">
@import 'devui-theme/styles-var/devui-var.scss';
.codeblock {
// background-color: $devui-global-bg;
min-height: calc(100vh - 144px - 104px - 52px);
}
.selectItem {
:deep(.devui-code-review__header) {
background-image: linear-gradient(to right, rgba(103, 136, 242, 0.2), transparent);
}
}
.left-tree {
border-right: 1px solid $devui-dividing-line;
position: sticky;
top: 10px;
min-height: calc(100vh - 144px - 104px);
}
.splitter-border {
:deep(.resizable .devui-splitter__collapse) {
position: fixed;
left: 320px;
top: 50%;
}
:deep(.none-resizable .devui-splitter__collapse) {
position: fixed;
left: 4px;
top: 50%;
}
}
.commitTitle {
font-size: 14px;
font-weight: 400;
}
.filter-diff-list {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
</style>