diff --git a/src/App.tsx b/src/App.tsx
index 9a09dc1..c08dba7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -31,6 +31,28 @@ function App() {
}
}, [])
+ const getCategoryList = useCallback(async () => {
+ const results = await Promise.allSettled(
+ ['news', 'job_type', 'job_area', 'job_unit', "file"]
+ .map(async (type) => {
+ const res = await appApi.getCategoryList(type);
+ return res.data.items.map((item:any) => {
+ return {
+ ...item,
+ type
+ }
+ })
+ })
+ ).then((results) => {
+ return results.map((result:any) => {
+ return result.value;
+ })
+ })
+
+ const categoryList = results.flat(Infinity)
+ useStore.getState().setCategoryList(categoryList);
+ }, [])
+
const getAppConfig = useCallback(async () => {
try {
const res = await appApi.getAppConfig();
@@ -44,6 +66,8 @@ function App() {
];
useStore.getState().setSupportLocales(supportLocales);
initState(config);
+
+ await getCategoryList()
} catch (error) {
console.log(error);
}
diff --git a/src/api/app.ts b/src/api/app.ts
index 92d1d65..4c394bb 100644
--- a/src/api/app.ts
+++ b/src/api/app.ts
@@ -24,14 +24,11 @@ const app = {
}
});
},
- getDocList() {
+ getDocList(params: any) {
return requests({
url: "/yt/api/doc",
method: "get",
- params: {
- page: 1,
- size: 1000,
- }
+ params: params
});
},
// 历程列表
diff --git a/src/components/layout/AnimateTopCard/index.module.css b/src/components/layout/AnimateTopCard/index.module.css
index 8608cbf..84f524c 100644
--- a/src/components/layout/AnimateTopCard/index.module.css
+++ b/src/components/layout/AnimateTopCard/index.module.css
@@ -3,6 +3,17 @@
background-position: center;
background-repeat: no-repeat;
+ .cardImage {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ position: absolute;
+ }
+ .cardVideo {
+ width: 100%;
+ height: 100%;
+ }
+
.cardMask {
position: absolute;
width: 100%;
@@ -20,6 +31,7 @@
.cardTitle span {
left: 30px;
transform: translateX(0);
+ text-align: left;
transition-delay: 0s;
}
}
@@ -45,8 +57,6 @@
transition-delay: 0.2s;
position: absolute;
top: calc(100% - 170px);
-
-
}
.cardTitle {
@@ -61,11 +71,20 @@
padding: 0 30px;
span {
+ display: inline-block;
transition: all 0.3s ease;
transition-delay: 0.2s;
position: absolute;
left: 50%;
transform: translateX(-50%);
+ width: max-content;
+ max-width: 100%;
+ box-sizing: border-box;
+ text-align: center;
+
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
}
diff --git a/src/components/layout/AnimateTopCard/index.tsx b/src/components/layout/AnimateTopCard/index.tsx
index e806159..0317351 100644
--- a/src/components/layout/AnimateTopCard/index.tsx
+++ b/src/components/layout/AnimateTopCard/index.tsx
@@ -1,25 +1,38 @@
import styles from './index.module.css';
import { Link } from 'react-router-dom';
+import { useStore } from '@/store';
type Data = {
title: string;
content: string;
- backgroundImage: string;
+ image: string;
+ video: string;
path: string;
- moreText: string;
+ moreText: 'moreText';
}
export default function AnimateTopCard({ data }: { data: Data }) {
+ const appConfig = useStore((s) => s.appConfig);
+ const others = appConfig?.__global__?.others ?? {};
+
return (
-
-
-
+
+ {
+ data.image ?
+

:
+
+ }
+
+
{data.title}
-
{data.content}
-
{data.moreText}
+
+ {
+ data.moreText &&
+
{others[data.moreText]}
+ }
-
+
);
}
\ No newline at end of file
diff --git a/src/layouts/Header.module.css b/src/layouts/Header.module.css
index ef455cb..d92ec02 100644
--- a/src/layouts/Header.module.css
+++ b/src/layouts/Header.module.css
@@ -23,16 +23,19 @@
display: block;
position: absolute;
top: 0;
+ left: 0;
width: 100%;
height: 0%;
background: #fff;
- transition: height 0.7s ease-in-out;
+ transition: height 0.5s ease-in-out;
}
.showDropPanel.header::before {
- height: 350%;
+ height: 100%;
+ transition: height 0.2s ease-in-out;
}
+
.whiteMode.header::before {
height: 100%;
}
@@ -44,11 +47,25 @@
transition-delay: none;
}
-.whiteMode .navLink, .showDropPanel .navLink {
- color: #222222;
+.keepNavDark.header::before {
+ height: 120px;
}
-.whiteMode, .showDropPanel {
+.whiteMode .navLink,
+.showDropPanel .navLink,
+.keepNavDark .navLink,
+.keepNavDark .langTrigger,
+.keepNavDark .searchBtn,
+.keepNavDark svg {
+ color: #222222;
+ /* transition: color 0.3s ease-in-out; */
+}
+.keepNavDark svg path {
+ fill: #222222;
+}
+
+.whiteMode,
+.showDropPanel {
.searchBtn {
color: #222222;
}
@@ -89,21 +106,27 @@
height: 1px;
background: #FFFFFF;
transform: scaleX(0);
- transform-origin: 90% 0; /* 交点:左侧 85%,右侧 15%,从此点向左右展开 */
+ transform-origin: 90% 0;
+ /* 交点:左侧 85%,右侧 15%,从此点向左右展开 */
transition: transform 1.5s ease-in-out;
}
+
.animate {
&.headerInner::after {
transform: scaleX(1);
}
+
.crossYline {
height: 100%;
}
}
-.whiteMode, .showDropPanel {
+
+.whiteMode,
+.showDropPanel {
.headerInner::after {
transform: scaleX(0);
}
+
.crossYline {
height: 0%;
}
@@ -116,6 +139,12 @@
line-height: 1.3;
}
+.commonMode {
+ .logo img {
+ transition: filter 0.5s steps(1);
+ }
+}
+
.headerRight {
display: flex;
align-items: center;
@@ -152,6 +181,7 @@
opacity: 0.9;
}
+
.actions {
display: flex;
height: 100%;
@@ -164,8 +194,10 @@
width: 1px;
height: 0%;
background: #FFFFFF;
- align-self: flex-end; /* 交点在最底部,从此点向上展开 */
- transform-origin: center bottom; /* 缩放从底部中心点展开 */
+ align-self: flex-end;
+ /* 交点在最底部,从此点向上展开 */
+ transform-origin: center bottom;
+ /* 缩放从底部中心点展开 */
transition: height 1s ease-in-out;
}
@@ -197,16 +229,18 @@
left: 0;
width: 100%;
height: 0;
- /* background: rgba(255, 255, 255, 0.9); */
- /* box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1); */
+ background: rgba(255, 255, 255, 0.9);
+ box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
padding: 0;
z-index: 1000;
overflow: hidden;
transition: height 0.5s ease-in-out, padding-top 0.5s ease-in-out;
+ /* transition-delay: 0.45s; */
&.visible {
height: 23.75rem;
padding-top: 1.25rem;
+
}
}
diff --git a/src/layouts/Header.tsx b/src/layouts/Header.tsx
index a0e5945..f009598 100644
--- a/src/layouts/Header.tsx
+++ b/src/layouts/Header.tsx
@@ -21,6 +21,8 @@ export default function Header() {
const [activeNav, setActiveNav] = useState("");
const [showDropPanel, setShowDropPanel] = useState(false);
+ const [keepNavDark, setKeepNavDark] = useState(false);
+ const leaveTimerRef = useRef
| null>(null);
const [hoverElLeft, setHoverElLeft] = useState(0);
const handleNavEnter = (e: any, path: string) => {
const left = e.target.offsetLeft;
@@ -29,6 +31,11 @@ export default function Header() {
setHoverElLeft(left + width / 2);
setActiveNav(path);
setShowDropPanel(true);
+ setKeepNavDark(true);
+ if (leaveTimerRef.current) {
+ clearTimeout(leaveTimerRef.current);
+ leaveTimerRef.current = null;
+ }
}
const activePanelItem = useMemo(() => {
@@ -65,12 +72,33 @@ export default function Header() {
}, 500);
}, [])
+ const handleHeaderMouseLeave = () => {
+ setShowDropPanel(false);
+ if (leaveTimerRef.current) {
+ clearTimeout(leaveTimerRef.current);
+ }
+ leaveTimerRef.current = setTimeout(() => {
+ setKeepNavDark(false);
+ leaveTimerRef.current = null;
+ }, 500);
+ };
+
+ const handleHeaderMouseEnter = () => {
+ if (leaveTimerRef.current) {
+ clearTimeout(leaveTimerRef.current);
+ leaveTimerRef.current = null;
+ }
+ };
+
return (
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
index 0b87e64..c48ec02 100644
--- a/src/pages/Home/index.tsx
+++ b/src/pages/Home/index.tsx
@@ -82,6 +82,7 @@ export default function Home() {
function News() {
const locale = useStore((s) => s.locale);
+ const categoryList = useStore((s) => s.categoryList);
const [newsData, setNewsData] = useState([]);
const videoRef = useRef(null);
const localNewsData = useMemo(() => {
@@ -122,16 +123,10 @@ function News() {
setNewsData(data);
});
}, []);
- const getCategoryList = useCallback(async () => {
- const res = await appApi.getCategoryList('news');
- const category_id = res.data.items.find((item: any) => item.name.includes('新闻资讯'))?.id;
- return category_id;
- }, []);
useEffect(() => {
- getCategoryList().then((category_id) => {
- handleSearch(category_id)
- });
+ const category_id = categoryList?.find((item: any) => item.name === '【首页】新闻资讯')?.id;
+ handleSearch(category_id)
}, [])
return (
diff --git a/src/pages/Join/Campus.tsx b/src/pages/Join/Campus.tsx
index 818895d..ae7256c 100644
--- a/src/pages/Join/Campus.tsx
+++ b/src/pages/Join/Campus.tsx
@@ -16,11 +16,14 @@ type JobItem = {
content: string;
labels: string[];
lang: string
-}
+};
+
+type SelectOption = { label: string; value: string };
export default function JoinCampus() {
const appConfig = useStore((s) => s.appConfig);
const supportLocales = useStore((s) => s.supportLocales);
+ const categoryList = useStore((s) => s.categoryList);
const locale = useStore((s) => s.locale);
const data = appConfig?.join?.campus;
const banner = data?.banner;
@@ -29,11 +32,11 @@ export default function JoinCampus() {
// 职业类别 业务领域 所属板块
const [jobType, setJobType] = useState('');
- const [jobTypeOptions, setJobTypeOptions] = useState([]);
+ const [jobTypeOptions, setJobTypeOptions] = useState([]);
const [businessArea, setBusinessArea] = useState('');
- const [businessAreaOptions, setBusinessAreaOptions] = useState([]);
+ const [businessAreaOptions, setBusinessAreaOptions] = useState([]);
const [businessPlate, setBusinessPlate] = useState('');
- const [businessPlateOptions, setBusinessPlateOptions] = useState([]);
+ const [businessPlateOptions, setBusinessPlateOptions] = useState([]);
const [page, setPage] = useState(1);
const [size] = useState(2 * supportLocales.length);
@@ -69,20 +72,16 @@ export default function JoinCampus() {
}, 500), []);
const getTypes = useCallback(() => {
- ['job_type', 'job_area', 'job_unit'].forEach(type => {
- appApi.getCategoryList(type).then((res) => {
- const items = res.data.items.map((item:any) => ({ label: item.name, value: item.id }));
- items.unshift({ label: "全部", value: "" });
- if (type === 'job_type') {
- setJobTypeOptions(items);
- } else if (type === 'job_area') {
- setBusinessAreaOptions(items);
- } else if (type === 'job_unit') {
- setBusinessPlateOptions(items);
- }
- })
- })
- }, []);
+ const jobTypeOptions: SelectOption[] =
+ categoryList?.filter((item: any) => item.type === 'job_type').map((item: any) => ({ label: item.name, value: String(item.id) })) ?? [];
+ const businessAreaOptions: SelectOption[] =
+ categoryList?.filter((item: any) => item.type === 'job_area').map((item: any) => ({ label: item.name, value: String(item.id) })) ?? [];
+ const businessPlateOptions: SelectOption[] =
+ categoryList?.filter((item: any) => item.type === 'job_unit').map((item: any) => ({ label: item.name, value: String(item.id) })) ?? [];
+ setJobTypeOptions(jobTypeOptions);
+ setBusinessAreaOptions(businessAreaOptions);
+ setBusinessPlateOptions(businessPlateOptions);
+ }, [categoryList]);
useEffect(() => {
refreshData();
diff --git a/src/pages/News/NewsPublic.tsx b/src/pages/News/NewsPublic.tsx
index f8328c6..890ce72 100644
--- a/src/pages/News/NewsPublic.tsx
+++ b/src/pages/News/NewsPublic.tsx
@@ -21,19 +21,20 @@ export default function NewsPublic() {
const appConfig = useStore((s) => s.appConfig);
const locale = useStore((s) => s.locale);
const supportLocales = useStore((s) => s.supportLocales);
+ const categoryList = useStore((s) => s.categoryList);
const data = appConfig?.news?.public;
const [page, setPage] = useState(1);
const [size] = useState(9 * supportLocales.length);
const [total, setTotal] = useState(0);
- const categoryIdRef = useRef('');
+ const categoryId = String(categoryList?.find((item: any) => item.name.includes('集团发布'))?.id ?? '');
const [newList, setNewList] = useState([]);
const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
const [searchValue, setSearchValue] = useState("");
const handleSearch = useCallback(() => {
appApi.getNewsList({ page, size, sort: "create_time DESC", title: searchValue,
- category_id: categoryIdRef.current ?? '',
+ category_id: categoryId,
}).then((res) => {
const data = res.data.items.map((item:any) => {
return {
@@ -54,19 +55,10 @@ export default function NewsPublic() {
return newList.filter(item => item.lang.toLowerCase() === locale.split('-')[0]);
}, [newList, locale]);
- const getCategoryList = useCallback(async () => {
- const res = await appApi.getCategoryList('news');
- const category_id = res.data.items.find((item: any) => item.name.includes('集团发布'))?.id;
- categoryIdRef.current = category_id;
- return category_id;
- }, []);
-
const banner = data?.banner;
useEffect(() => {
- getCategoryList().then(() => {
- handleSearch();
- })
+ handleSearch();
}, [page, size]);
if (!data) return null;
diff --git a/src/pages/Social/Foundation.tsx b/src/pages/Social/Foundation.tsx
index cff521a..dbb1370 100644
--- a/src/pages/Social/Foundation.tsx
+++ b/src/pages/Social/Foundation.tsx
@@ -1,5 +1,5 @@
import Banner, { type BannerConfig } from "@/components/Banner";
-import { useState } from "react";
+import { useEffect, useMemo, useState } from "react";
import ParagraphSection from "@/components/layout/ParagraphSection";
import Section from "@/components/layout/Section";
import AnimateTopCard from "@/components/layout/AnimateTopCard";
@@ -7,20 +7,44 @@ import BottomTabs from "@/components/layout/BottomTabsSection/BottomTabs";
import { useStore } from "@/store";
import styles from "./Foundation.module.css";
import TopTabsSection from "@/components/layout/TopTabsSection";
-
+import appApi from "@/api/app";
export default function Foundation() {
const appConfig = useStore((s) => s.appConfig);
const data = appConfig?.social?.foundation;
+ const locale = useStore((s) => s.locale)
+ const categoryList = useStore((s) => s.categoryList)
const [activeIndex, setActiveIndex] = useState(0);
- if (!data) return null;
-
const banner = data.banner;
const section1Data = data.section1Data;
const section2Data = data.section2Data;
const section3Data = data.section3Data;
const section4Data = data.section4Data;
+
+ const [newsList, setNewsList] = useState([]);
+ const localNewsList = useMemo(() => {
+ return newsList.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]);
+ }, [newsList, locale])
+ useEffect(() => {
+ appApi.getNewsList({ page: 1, size: 1000, sort: "create_time DESC",
+ category_id: String(categoryList?.find((item: any) => item.name.includes('【可持续发展】社会责任案例集'))?.id ?? ''),
+ }).then((res:any) => {
+ setNewsList(res.data.items.map((item:any) => {
+ return {
+ id: item.id,
+ title: item.title,
+ content: item.content,
+ path: item.path,
+ image: item.covers_show === 'image' ? item.covers.image[0] : '',
+ video: item.covers_show === 'video' ? item.covers.video[0] : '',
+ lang: item.lang,
+ moreText: 'moreText',
+ }
+ }));
+ });
+ }, [])
+
return (
- {section2Data.items?.map((item: { title: string; content?: string; backgroundImage?: string; path?: string; moreText?: string }, index: number) => (
+ {localNewsList?.map((item: any, index: number) => (
))}
diff --git a/src/pages/Social/Sustainability.module.css b/src/pages/Social/Sustainability.module.css
index 1f973f7..ff6d43f 100644
--- a/src/pages/Social/Sustainability.module.css
+++ b/src/pages/Social/Sustainability.module.css
@@ -140,6 +140,7 @@
background-size: cover;
background-position: center;
background-repeat: no-repeat;
+ cursor: pointer;
}
.socialResponsibilityReportItemTitle {
diff --git a/src/pages/Social/Sustainability.tsx b/src/pages/Social/Sustainability.tsx
index 26e9d37..1d58154 100644
--- a/src/pages/Social/Sustainability.tsx
+++ b/src/pages/Social/Sustainability.tsx
@@ -1,18 +1,30 @@
import Banner, { type BannerConfig } from "@/components/Banner";
-import { useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
import ParagraphSection from "@/components/layout/ParagraphSection";
import ColumnXGrids from "@/components/layout/ColumnXGrids";
import Section from "@/components/layout/Section";
import AnimateTopCard from "@/components/layout/AnimateTopCard";
import { useStore } from "@/store";
import styles from "./Sustainability.module.css";
+import appApi from "@/api/app";
+
+type NewsItem = {
+ id: number;
+ title: string;
+ createTime: string;
+ image: string;
+ video: string;
+ lang: "ZH" | "EN"
+}
+
+const LANG_LEN = 2
export default function Sustainability() {
const appConfig = useStore((s) => s.appConfig);
+ const categoryList = useStore((s) => s.categoryList);
+ const locale = useStore((s) => s.locale);
const data = appConfig?.social?.sustainability;
- const [sliceIndex, setSliceIndex] = useState(4);
-
- if (!data) return null;
+ const others = appConfig?.__global__?.others ?? {};
const banner = data.banner;
const section1Data = data.section1Data;
@@ -22,6 +34,81 @@ export default function Sustainability() {
const columnXGridsData = section2Data;
+
+ // 社会责任案例新闻数据
+ const [newsItems, setNewsItems] = useState([]);
+ const localNewsItems = useMemo(() => {
+ return newsItems.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]);
+ }, [newsItems, locale])
+
+ const getNewsList = useCallback(() => {
+ appApi.getNewsList({
+ page: 1,
+ size: 8,
+ sort: "create_time DESC",
+ category_id: String(categoryList?.find((item: any) => item.name.includes('【可持续发展】社会责任案例集'))?.id ?? ''),
+ }).then((res) => {
+ const items = res.data.items.map((item: any) => {
+ return {
+ id: item.id,
+ title: item.title,
+ content: item.content,
+ createTime: item.create_time,
+ image: item.covers_show === 'image' ? item.covers.image[0] : '',
+ video: item.covers_show === 'video' ? item.covers.video[0] : '',
+ path: `/news/detail/${item.id}`,
+ lang: item.lang,
+ moreText: 'moreText',
+ }
+ })
+ setNewsItems(items as NewsItem[])
+ });
+ }, [])
+
+
+ // 社会责任报告
+ type ReportItem = {
+ id: number;
+ name: string;
+ path: string;
+ cover: string;
+ }
+ const [reportItems, setReportItems] = useState([]);
+ const [page, setPage] = useState(1);
+ const [size, setSize] = useState(8);
+ const [total, setTotal] = useState(0);
+ const localReportItems = useMemo(() => {
+ return reportItems.filter((item: any) => item.lang.toLowerCase() === locale.split('-')[0]);
+ }, [reportItems, locale])
+ const getReportList = useCallback(async () => {
+ const res = await appApi.getDocList({
+ page,
+ size,
+ category_id: String(categoryList?.find((item: any) => item.name.includes('社会责任报告'))?.id ?? '')
+ })
+ setTotal(res.data.total / LANG_LEN)
+ setReportItems((prev) => {
+ let items = [...prev, ...res.data.items]
+ const resItems: any[] = []
+ // 去重 id lang 相同
+ items.forEach((item: any) => {
+ if(!resItems.some((i: any) => i.id === item.id && i.lang === item.lang)) {
+ resItems.push(item)
+ }
+ })
+ console.log('---resItems', resItems)
+ return resItems
+ })
+ }, [page, size])
+
+ useEffect(() => {
+ getNewsList()
+ }, []);
+
+ useEffect(() => {
+ getReportList()
+ }, [page])
+
return (
- {section3Data.items?.map((item: any, index: number) => (
+ {localNewsItems?.map((item: any, index: number) => (
@@ -67,32 +153,36 @@ export default function Sustainability() {
maskBackground="#F7FBFF"
>
- {section4Data.items?.slice(0, sliceIndex).map((item: any, index: number) => (
+ {localReportItems?.map((item: any, index: number) => (
{
+ window.open(item.path, '_blank')
+ }}
/>
- {item.title}
+ {item.name}
))}
- setSliceIndex(
- sliceIndex < (section4Data.items?.length ?? 0)
- ? sliceIndex + 4
- : 4
- )
- }
+ onClick={() => {
+ if(localReportItems.length < total) {
+ setPage(page + 1)
+ } else {
+ setReportItems(prev => [...prev.slice(0, 8)])
+ setPage(1)
+ }
+ }}
>
- {sliceIndex < (section4Data.items?.length ?? 0) ? "了解更多" : "收起"}
+ {localReportItems.length < total ? "了解更多" : "收起"}
)}
diff --git a/src/store/index.ts b/src/store/index.ts
index 2c36639..91c38e5 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -15,10 +15,16 @@ interface StoreState {
locale: LocaleKey;
i18nData: I18nData | null;
appConfig: AppConfig | null;
+ categoryList: {
+ id: number;
+ name: string;
+ type: string;
+ }[] | null;
supportLocales: SupportLocale[];
token: string | null;
setLocale: (locale: LocaleKey) => void;
setAppConfig: (data: I18nData) => void;
+ setCategoryList: (list: any[]) => void;
setToken: (token: string | null) => void;
setSupportLocales: (locales: SupportLocale[]) => void;
}
@@ -29,6 +35,7 @@ export const useStore = create
()(
locale: 'zh-CN',
i18nData: null,
appConfig: null,
+ categoryList: null,
supportLocales: [],
token: null,
setLocale: (locale) =>
@@ -41,6 +48,7 @@ export const useStore = create()(
i18nData: data,
appConfig: data[state.locale] ?? data['en-US'] ?? data['zh-CN'] ?? null,
})),
+ setCategoryList: (list) => set({ categoryList: list }),
setToken: (token) => set({ token }),
setSupportLocales: (locales: SupportLocale[]) => set({ supportLocales: locales }),
}),
@@ -62,6 +70,7 @@ export const useStore = create()(
locale,
i18nData: { "zh-CN": legacy, "en-US": legacy },
appConfig: legacy,
+ categoryList: p.categoryList,
};
},
partialize: (s) => ({
@@ -70,6 +79,7 @@ export const useStore = create()(
token: s.token,
appConfig: s.i18nData?.[s.locale] ?? s.appConfig,
supportLocales: s.supportLocales,
+ categoryList: s.categoryList,
}),
}
)
diff --git a/src/utils/index.ts b/src/utils/index.ts
index b47fb09..72b8e18 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,3 +1,6 @@
+import { useStore } from "zustand";
+import { LocaleKey } from "@/type";
+
// debounce
export const debounce = (func: (...args: any[]) => void, delay: number) => {
let timeout: NodeJS.Timeout;