+
27 ? str : ''} placement="right">
+
{str}
);
},
filters: useContributionTypeLsit(),
+ defaultFilteredValue:
+ defaultFilterOpts.find((i) => i.type === 'contribution_type')?.values ||
+ null,
filterMode: 'tree',
ellipsis: true,
align: 'left',
@@ -228,15 +258,27 @@ const MetricTable: React.FC<{
},
];
return (
-
+ <>
+
+
+ contributorDownload(
+ query,
+ t('analyze:metric_detail:contributor_data_table')
+ )
+ }
+ />
+
+
+ >
);
};
export default MetricTable;
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts
index 6dc01b10..ec07f9eb 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/contribution.ts
@@ -1,5 +1,6 @@
import { useTranslation } from 'next-i18next';
+// 领域画像 option
const useContributionTypeMap = () => {
const { t } = useTranslation();
return {
@@ -113,6 +114,7 @@ const useContributionTypeMap = () => {
},
};
};
+// 领域画像 filter
export const useContributionTypeLsit = () => {
const obj = useContributionTypeMap();
const result = [];
@@ -133,23 +135,32 @@ export const useContributionTypeLsit = () => {
}
return result;
};
+// 领域画像 i18n(表格字段翻译)
export const useGetContributionTypeI18n = () => {
const obj = useContributionTypeMap();
const result = {};
-
- function traverseObject(obj) {
+ const colors = [
+ '#007ACC', // 蓝色
+ '#008000', // 绿色
+ '#FFA500', // 橙色
+ '#FF1493', // 粉红
+ '#800080', // 紫色
+ ];
+ function traverseObject(obj, color) {
for (const key in obj) {
if (typeof obj[key] === 'object') {
- traverseObject(obj[key]);
+ const c = colors.shift() || '#ccc';
+ traverseObject(obj[key], c);
} else {
- result[key] = obj[key];
+ result[key] = { text: obj[key], color };
}
}
}
-
- traverseObject(obj);
+ traverseObject(obj, '#ccc');
return result;
};
+
+// 里程画像 i18n(表格字段翻译)
export const useMileageOptions = () => {
const { t } = useTranslation();
@@ -159,6 +170,8 @@ export const useMileageOptions = () => {
{ label: t('analyze:metric_detail:guest'), value: 'guest' },
];
};
+
+//角色画像 option
export const useEcologicalType = () => {
const { t } = useTranslation();
@@ -181,7 +194,7 @@ export const useEcologicalType = () => {
},
];
};
-
+//角色画像 i18n
export const useGetEcologicalText = () => {
const { t } = useTranslation();
const ecologicalOptions = useEcologicalType();
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/index.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/index.tsx
index cbf57b9e..958a5388 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/index.tsx
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricContributor/index.tsx
@@ -12,17 +12,27 @@ import { AiOutlineQuestionCircle } from 'react-icons/ai';
import Tooltip from '@common/components/Tooltip';
import useLabelStatus from '@modules/analyze/hooks/useLabelStatus';
import BaseCard from '@common/components/BaseCard';
+import { useRouter } from 'next/router';
+import { useHandleQueryParams } from '@modules/analyze/hooks/useHandleQueryParams';
const MetricContributor = () => {
const { t } = useTranslation();
+ const router = useRouter();
+ const { handleQueryParams } = useHandleQueryParams();
const { verifiedItems } = useLabelStatus();
const { label, level } = verifiedItems[0];
- const [tab, setTab] = useState('1');
+ const queryCard = router.query?.card as string;
+ const [tab, setTab] = useState(queryCard || '1');
const { timeStart, timeEnd } = useVerifyDateRange();
const options = useMileageOptions();
- const [mileage, setMileage] = useState
(['core', 'regular']);
+ const queryMileage = router.query?.mileage as string;
+ const defaultMileage = queryMileage
+ ? JSON.parse(queryMileage)
+ : ['core', 'regular'];
+ const [mileage, setMileage] = useState(defaultMileage);
const onChange = (checkedValues: string[]) => {
setMileage(checkedValues);
+ handleQueryParams({ mileage: JSON.stringify(checkedValues) });
};
let source;
switch (tab) {
@@ -63,6 +73,15 @@ const MetricContributor = () => {
break;
}
default: {
+ source = (
+
+ );
break;
}
}
@@ -79,6 +98,7 @@ const MetricContributor = () => {
value={tab}
onChange={(e, v) => {
setTab(v);
+ handleQueryParams({ card: v });
}}
aria-label="Tabs where selection follows focus"
selectionFollowsFocus
@@ -110,7 +130,7 @@ const MetricContributor = () => {
-
+
{t('analyze:metric_detail:milestone_persona_filter')}
{
:
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueComments.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueComments.tsx
index bedd126f..6cbe47b5 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueComments.tsx
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/IssueComments.tsx
@@ -3,7 +3,6 @@ import { useIssueCommentQuery } from '@oss-compass/graphql';
import client from '@common/gqlClient';
import { useTranslation } from 'next-i18next';
import MetricChart from '@modules/analyze/DataView/MetricDetail/MetricChart';
-import type { EChartsOption } from 'echarts';
import { getPieOption } from '@modules/analyze/DataView/MetricDetail/metricChartOption';
const IssueCompletion: React.FC<{
@@ -39,7 +38,7 @@ const IssueCompletion: React.FC<{
const option = getPieOption({ seriesData: getSeries });
return (
-
+
+
+ <>
+
+
+ issueDownload(query, t('analyze:metric_detail:issue_data_table'))
+ }
+ />
+
+
+ >
);
};
export default MetricTable;
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/index.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/index.tsx
index bb1513a3..86b59be2 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/index.tsx
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricIssue/index.tsx
@@ -8,13 +8,18 @@ import IssueComments from './IssueComments';
import { useTranslation } from 'next-i18next';
import useLabelStatus from '@modules/analyze/hooks/useLabelStatus';
import BaseCard from '@common/components/BaseCard';
+import { useRouter } from 'next/router';
+import { useHandleQueryParams } from '@modules/analyze/hooks/useHandleQueryParams';
const MetricIssue = () => {
+ const router = useRouter();
+ const { handleQueryParams } = useHandleQueryParams();
const { verifiedItems } = useLabelStatus();
+ const { timeStart, timeEnd } = useVerifyDateRange();
const { label, level } = verifiedItems[0];
const { t } = useTranslation();
- const [tab, setTab] = useState('1');
- const { timeStart, timeEnd } = useVerifyDateRange();
+ const queryCard = router.query?.card as string;
+ const [tab, setTab] = useState(queryCard || '1');
let source;
switch (tab) {
case '1': {
@@ -51,20 +56,29 @@ const MetricIssue = () => {
break;
}
default: {
+ source = (
+
+ );
break;
}
}
return (
{
setTab(v);
+ handleQueryParams({ card: v });
}}
aria-label="Tabs where selection follows focus"
selectionFollowsFocus
@@ -95,7 +109,7 @@ const MetricIssue = () => {
/>
- {source}
+ {source}
);
};
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/PrComments.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/PrComments.tsx
index 9208b1d2..4768867f 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/PrComments.tsx
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/PrComments.tsx
@@ -37,7 +37,7 @@ const PrComments: React.FC<{
const option = getPieOption({ seriesData: getSeries });
return (
-
+
+
+ <>
+
+
+ prDownload(query, t('analyze:metric_detail:pr_data_table'))
+ }
+ />
+
+
+ >
);
};
export default MetricTable;
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/index.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/index.tsx
index fc65c0ff..807b12f5 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/index.tsx
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/MetricPr/index.tsx
@@ -8,12 +8,17 @@ import PrComments from './PrComments';
import { useTranslation } from 'next-i18next';
import useLabelStatus from '@modules/analyze/hooks/useLabelStatus';
import BaseCard from '@common/components/BaseCard';
+import { useRouter } from 'next/router';
+import { useHandleQueryParams } from '@modules/analyze/hooks/useHandleQueryParams';
const MetricPr = () => {
+ const { t } = useTranslation();
+ const router = useRouter();
+ const { handleQueryParams } = useHandleQueryParams();
const { verifiedItems } = useLabelStatus();
const { label, level } = verifiedItems[0];
- const { t } = useTranslation();
- const [tab, setTab] = useState('1');
+ const queryCard = router.query?.card as string;
+ const [tab, setTab] = useState(queryCard || '1');
const { timeStart, timeEnd } = useVerifyDateRange();
let source;
switch (tab) {
@@ -51,20 +56,29 @@ const MetricPr = () => {
break;
}
default: {
+ source = (
+
+ );
break;
}
}
return (
{
setTab(v);
+ handleQueryParams({ card: v });
}}
aria-label="Tabs where selection follows focus"
selectionFollowsFocus
@@ -94,7 +108,7 @@ const MetricPr = () => {
value="3"
/>
- {source}
+ {source}
);
};
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/index.tsx b/apps/web/src/modules/analyze/DataView/MetricDetail/index.tsx
index f15b7a32..0b252ea5 100644
--- a/apps/web/src/modules/analyze/DataView/MetricDetail/index.tsx
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/index.tsx
@@ -5,7 +5,6 @@ import MetricContributor from './MetricContributor';
import MetricIssue from './MetricIssue';
import MetricPr from './MetricPr';
import { AiOutlineLeftCircle } from 'react-icons/ai';
-import { useRouter } from 'next/router';
import MerticDatePicker from '@modules/analyze/components/NavBar/MerticDatePicker';
import useLabelStatus from '@modules/analyze/hooks/useLabelStatus';
import { withErrorBoundary } from 'react-error-boundary';
@@ -13,6 +12,8 @@ import ErrorFallback from '@common/components/ErrorFallback';
import useVerifyDetailRange from '@modules/analyze/hooks/useVerifyDetailRange';
import LoadingAnalysis from '@modules/analyze/DataView/Status/LoadingAnalysis';
import LabelItems from '@modules/analyze/components/NavBar/LabelItems';
+import { useRouter } from 'next/router';
+import { useHandleQueryParams } from '@modules/analyze/hooks/useHandleQueryParams';
const VerifyMetricDetail = () => {
const { isLoading } = useVerifyDetailRange();
@@ -24,9 +25,11 @@ const VerifyMetricDetail = () => {
const MetricDetail = () => {
const { t } = useTranslation();
const router = useRouter();
+ const { handleQueryParams } = useHandleQueryParams();
const slugs = router.query.slugs;
+ const queryTab = router.query?.tab as string;
const { isLoading, verifiedItems } = useLabelStatus();
- const [tab, setTab] = useState
('contributor');
+ const [tab, setTab] = useState(queryTab || 'contributor');
if (isLoading || verifiedItems.length > 1) {
return null;
}
@@ -77,7 +80,14 @@ const MetricDetail = () => {
- setTab(v)} />
+ {
+ setTab(v);
+ handleQueryParams({ tab: v });
+ }}
+ />
diff --git a/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts b/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts
new file mode 100644
index 00000000..23eb2715
--- /dev/null
+++ b/apps/web/src/modules/analyze/DataView/MetricDetail/tableDownload.ts
@@ -0,0 +1,54 @@
+import axios from 'axios';
+
+const apiDownloadFiles = (res, fileName) => {
+ if (res.data.type === 'application/json') {
+ // this.$message({
+ // type: 'error',
+ // message: '下载失败,文件不存在或权限不足',
+ // });
+ } else {
+ let blob = new Blob([res.data]);
+ let link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = fileName + '.csv';
+ link.style.display = 'none';
+ document.body.appendChild(link);
+ link.click();
+ window.URL.revokeObjectURL(link.href);
+ document.body.removeChild(link);
+ }
+};
+
+export const contributorDownload = (query, fileName) => {
+ return axios
+ .post('/api/v1/contributor/export', query, {
+ headers: {
+ accept: 'application/json',
+ },
+ })
+ .then((res) => {
+ apiDownloadFiles(res, fileName);
+ });
+};
+export const issueDownload = (query, fileName) => {
+ return axios
+ .post('/api/v1/contributor/export', query, {
+ headers: {
+ accept: 'application/json',
+ },
+ })
+ .then((res) => {
+ apiDownloadFiles(res, fileName);
+ });
+};
+export const prDownload = (query, fileName) => {
+ return axios
+ .post('/api/v1/contributor/export', query, {
+ headers: {
+ accept: 'application/json',
+ },
+ })
+ .then((res) => {
+ apiDownloadFiles(res, fileName);
+ });
+};
diff --git a/apps/web/src/modules/analyze/DataView/index.tsx b/apps/web/src/modules/analyze/DataView/index.tsx
index b54714ec..9d731ec5 100644
--- a/apps/web/src/modules/analyze/DataView/index.tsx
+++ b/apps/web/src/modules/analyze/DataView/index.tsx
@@ -29,28 +29,5 @@ const DataView = () => {
);
};
-// const CollaborationDataView = () => {
-// return (
-// <>
-//
-//
-// >
-// );
-// };
-// const ContributorDataView = () => {
-// const { isLoading } = useVerifyDetailRange();
-
-// if (isLoading) {
-// return ;
-// }
-// return (
-// <>
-//
-//
-//
-//
-// >
-// );
-// };
export default DataView;
diff --git a/apps/web/src/modules/analyze/hooks/useHandleQueryParams.ts b/apps/web/src/modules/analyze/hooks/useHandleQueryParams.ts
new file mode 100644
index 00000000..537bc0e5
--- /dev/null
+++ b/apps/web/src/modules/analyze/hooks/useHandleQueryParams.ts
@@ -0,0 +1,15 @@
+import { useRouter } from 'next/router';
+
+// 修改或新增查询参数
+export const useHandleQueryParams = () => {
+ const router = useRouter();
+ const handleQueryParams = (newParams) => {
+ const { pathname, query } = router;
+ const newQueryParams = { ...query, ...newParams };
+ router.push({
+ pathname,
+ query: newQueryParams,
+ });
+ };
+ return { handleQueryParams };
+};
diff --git a/apps/web/src/modules/analyze/hooks/useVerifyDetailRange.ts b/apps/web/src/modules/analyze/hooks/useVerifyDetailRange.ts
index 61cfca87..f8e49df3 100644
--- a/apps/web/src/modules/analyze/hooks/useVerifyDetailRange.ts
+++ b/apps/web/src/modules/analyze/hooks/useVerifyDetailRange.ts
@@ -5,9 +5,15 @@ import useExtractShortIds from './useExtractShortIds';
const useVerifyDetailRange = () => {
const { shortIds } = useExtractShortIds();
- const { data, isLoading } = useVerifyDetailDataRangeQuery(client, {
- shortCode: shortIds[0],
- });
+ const { data, isLoading } = useVerifyDetailDataRangeQuery(
+ client,
+ {
+ shortCode: shortIds[0],
+ },
+ {
+ staleTime: 300000, // 5 minutes
+ }
+ );
return { isLoading, data };
};