win-chart 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/.eslintrc.json +3 -0
  2. package/.prettierrc.json +5 -0
  3. package/README.md +0 -0
  4. package/localhost-key.pem +28 -0
  5. package/localhost.pem +25 -0
  6. package/package.json +65 -0
  7. package/rsbuild.config.ts +48 -0
  8. package/src/api/README.md +15 -0
  9. package/src/api/index.js +5 -0
  10. package/src/api/layout/index.js +32 -0
  11. package/src/api/request.ts +87 -0
  12. package/src/api/url-map/index.js +10 -0
  13. package/src/api/user/index.js +12 -0
  14. package/src/components/FilterContext.tsx +6 -0
  15. package/src/components/GlobalStyle.tsx +25 -0
  16. package/src/components/HalfScreenBrowser.tsx +95 -0
  17. package/src/components/ImgBox.tsx +19 -0
  18. package/src/components/JumpBtn.tsx +38 -0
  19. package/src/components/MCardWrapper.tsx +33 -0
  20. package/src/components/NoContent/index.tsx +85 -0
  21. package/src/components/ResponsiveReactGridLayout.tsx +218 -0
  22. package/src/components/SliderDecoration.tsx +27 -0
  23. package/src/components/VisionUserConfigContext.ts +6 -0
  24. package/src/components/WinMenu/README.md +11 -0
  25. package/src/components/WinMenu/WinHeader.tsx +5 -0
  26. package/src/components/WinMenu/components/ExtraBox.tsx +61 -0
  27. package/src/components/WinMenu/components/FullMenuBox.tsx +80 -0
  28. package/src/components/WinMenu/components/FullMenuContainer.tsx +50 -0
  29. package/src/components/WinMenu/components/FullMenuItem.tsx +128 -0
  30. package/src/components/WinMenu/components/LangSwitch.tsx +84 -0
  31. package/src/components/WinMenu/components/LogoBox.tsx +29 -0
  32. package/src/components/WinMenu/components/PinDialog.tsx +72 -0
  33. package/src/components/WinMenu/components/PinnedMenuBox.tsx +183 -0
  34. package/src/components/WinMenu/components/UserBox.tsx +83 -0
  35. package/src/components/WinMenu/hooks/useUserInfo.ts +21 -0
  36. package/src/components/WinMenu/hooks/useUserMenu.ts +35 -0
  37. package/src/components/WinMenu/index.tsx +100 -0
  38. package/src/components/WinMenu/services/WinService.ts +79 -0
  39. package/src/components/WinMenu/services/request.ts +53 -0
  40. package/src/components/WinMenu/utils/const.ts +80 -0
  41. package/src/components/WinMenu/utils/enum.ts +19 -0
  42. package/src/components/WinMenu/utils/interface.ts +61 -0
  43. package/src/components/WinMenu/utils/map.ts +39 -0
  44. package/src/components/WinMenu/utils/tool.ts +142 -0
  45. package/src/components/WinMenu/utils/type.ts +1 -0
  46. package/src/components/hover-view.tsx +48 -0
  47. package/src/components/icon.tsx +44 -0
  48. package/src/components/indicator-remark.tsx +25 -0
  49. package/src/components/none-content.tsx +93 -0
  50. package/src/components/panel-tab.tsx +150 -0
  51. package/src/components/panel-title-tab.tsx +60 -0
  52. package/src/components/win-card/components/ChartContent.tsx +47 -0
  53. package/src/components/win-card/components/Indicator.tsx +42 -0
  54. package/src/components/win-card/components/IndicatorArea.tsx +63 -0
  55. package/src/components/win-card/components/IndicatorInfo.tsx +78 -0
  56. package/src/components/win-card/components/MatterContent.tsx +121 -0
  57. package/src/components/win-card/components/MatterList.tsx +141 -0
  58. package/src/components/win-card/components/NoticeContent.tsx +73 -0
  59. package/src/components/win-card/components/NoticeList.tsx +68 -0
  60. package/src/components/win-card/components/SelectDecoration.tsx +34 -0
  61. package/src/components/win-card/components/WinRankContent.tsx +102 -0
  62. package/src/components/win-card/index.tsx +298 -0
  63. package/src/components/win-card/utils/interface.ts +12 -0
  64. package/src/components/win-card/utils/tool.ts +11 -0
  65. package/src/components/win-card-mobile/components/ChartContent.tsx +47 -0
  66. package/src/components/win-card-mobile/components/DualLineBar.tsx +61 -0
  67. package/src/components/win-card-mobile/components/Indicator.tsx +42 -0
  68. package/src/components/win-card-mobile/components/IndicatorArea.tsx +65 -0
  69. package/src/components/win-card-mobile/components/IndicatorInfo.tsx +83 -0
  70. package/src/components/win-card-mobile/components/SelectDecoration.tsx +28 -0
  71. package/src/components/win-card-mobile/index.tsx +235 -0
  72. package/src/components/win-card-mobile/utils/interface.ts +12 -0
  73. package/src/components/win-card-mobile/utils/tool.ts +9 -0
  74. package/src/components/win-chart/components/chart-wrapper.tsx +5 -0
  75. package/src/components/win-chart/index.tsx +59 -0
  76. package/src/components/win-chart/theme/win-dark.json +372 -0
  77. package/src/components/win-chart/theme/win-light.json +372 -0
  78. package/src/components/win-chart/utils/const.ts +37 -0
  79. package/src/components/win-chart/utils/getAreaSpec.ts +150 -0
  80. package/src/components/win-chart/utils/getBarSpec.ts +60 -0
  81. package/src/components/win-chart/utils/getChartOptions.ts +59 -0
  82. package/src/components/win-chart/utils/getColumnSpec.ts +112 -0
  83. package/src/components/win-chart/utils/getDualSpec.ts +79 -0
  84. package/src/components/win-chart/utils/getFunnelSpec.ts +70 -0
  85. package/src/components/win-chart/utils/getLineSpec.ts +53 -0
  86. package/src/components/win-chart/utils/getPieSpec.ts +124 -0
  87. package/src/components/win-chart/utils/getRadarSpec.ts +87 -0
  88. package/src/components/win-chart/utils/tool.ts +163 -0
  89. package/src/components/win-chart/utils/type.ts +96 -0
  90. package/src/components/win-icon/index.tsx +97 -0
  91. package/src/components/win-v/components/EmptyBox.tsx +9 -0
  92. package/src/components/win-v/components/VDialog.tsx +173 -0
  93. package/src/components/win-v/components/VDialogBtn.tsx +119 -0
  94. package/src/components/win-v/components/VDialogMsg.tsx +325 -0
  95. package/src/components/win-v/components/VInput.tsx +15 -0
  96. package/src/components/win-v/components/VRemindMsgList.tsx +303 -0
  97. package/src/components/win-v/components/VRobot.tsx +97 -0
  98. package/src/components/win-v/hooks/useVDialogCore.ts +102 -0
  99. package/src/components/win-v/hooks/useVDialogOperate.ts +132 -0
  100. package/src/components/win-v/hooks/useVDialogState.ts +52 -0
  101. package/src/components/win-v/hooks/useVRemindMsgShow.ts +15 -0
  102. package/src/components/win-v/index.tsx +205 -0
  103. package/src/components/win-wrappers/card-wrapper.tsx +103 -0
  104. package/src/components/win-wrappers/dot-wrapper.tsx +18 -0
  105. package/src/components/win-wrappers/iframe-wrapper.tsx +7 -0
  106. package/src/components/win-wrappers/request-wrapper.tsx +190 -0
  107. package/src/global.d.ts +53 -0
  108. package/src/hooks/useCardOptionList.ts +23 -0
  109. package/src/hooks/useCostFiltersDicList.ts +22 -0
  110. package/src/hooks/useFbiUrl.ts +22 -0
  111. package/src/hooks/useHalfScreenState.ts +50 -0
  112. package/src/hooks/useHasIntersected.ts +36 -0
  113. package/src/hooks/useIndicatorCardInfo.ts +26 -0
  114. package/src/hooks/useIndicatorCardQueryList.ts +22 -0
  115. package/src/hooks/useIndicatorClassifyList.ts +25 -0
  116. package/src/hooks/useIndicatorDetailsDateList.ts +22 -0
  117. package/src/hooks/useIndicatorDetailsInfo.ts +49 -0
  118. package/src/hooks/useIndicatorDetailsTrend.ts +25 -0
  119. package/src/hooks/useIndicatorDicList.ts +22 -0
  120. package/src/hooks/useIndicatorLabelList.ts +20 -0
  121. package/src/hooks/useIndicatorList.ts +24 -0
  122. package/src/hooks/useIndicatorRangeList.ts +20 -0
  123. package/src/hooks/useIndicatorTypeList.ts +20 -0
  124. package/src/hooks/useIndicatorUnitList.ts +21 -0
  125. package/src/hooks/useListenQuery.ts +29 -0
  126. package/src/hooks/useMobile.ts +58 -0
  127. package/src/hooks/useNotice.ts +42 -0
  128. package/src/hooks/useOrgLevelList.ts +23 -0
  129. package/src/hooks/usePageSelection.ts +121 -0
  130. package/src/hooks/usePageState.ts +22 -0
  131. package/src/hooks/usePendingList.ts +27 -0
  132. package/src/hooks/usePortalPageInfo.ts +43 -0
  133. package/src/hooks/useRefreshByLocationChange.ts +16 -0
  134. package/src/hooks/useReportIndicatorList.ts +21 -0
  135. package/src/hooks/useReportInfo.ts +45 -0
  136. package/src/hooks/useReportPersonList.ts +21 -0
  137. package/src/hooks/useScriptLoader.ts +22 -0
  138. package/src/hooks/useUnreadMsgList.ts +26 -0
  139. package/src/hooks/useUserAvatar.ts +23 -0
  140. package/src/hooks/useVReportInfo.ts +50 -0
  141. package/src/hooks/useVisionUserConfig.ts +25 -0
  142. package/src/hooks/useWorkbenchOptions.ts +63 -0
  143. package/src/index.tsx +20 -0
  144. package/src/services/CardService.ts +91 -0
  145. package/src/services/CommonService.ts +23 -0
  146. package/src/services/CostService.ts +56 -0
  147. package/src/services/DialogService.ts +74 -0
  148. package/src/services/IndicatorService.ts +406 -0
  149. package/src/services/PageService.ts +204 -0
  150. package/src/services/ReportService.ts +335 -0
  151. package/src/services/WorkbenchService.ts +411 -0
  152. package/src/styles/README.md +12 -0
  153. package/src/styles/index.scss +9 -0
  154. package/src/styles/mixins/index.scss +25 -0
  155. package/src/styles/next-cover.scss +4 -0
  156. package/src/styles/normalize.scss +27 -0
  157. package/src/styles/utilities/index.scss +5 -0
  158. package/src/styles/vars/index.scss +17 -0
  159. package/src/types/enum-workbench.ts +29 -0
  160. package/src/types/enum.ts +156 -0
  161. package/src/types/index.ts +19 -0
  162. package/src/types/indicator.ts +299 -0
  163. package/src/types/interface.ts +303 -0
  164. package/src/types/portal.ts +211 -0
  165. package/src/types/report.ts +28 -0
  166. package/src/types/type.ts +11 -0
  167. package/src/types/user.ts +28 -0
  168. package/src/utils/README.md +4 -0
  169. package/src/utils/arms.ts +59 -0
  170. package/src/utils/arr.ts +123 -0
  171. package/src/utils/const-workbench.ts +9 -0
  172. package/src/utils/const.ts +18 -0
  173. package/src/utils/index.ts +18 -0
  174. package/src/utils/init.ts +5 -0
  175. package/src/utils/map-workbench.ts +66 -0
  176. package/src/utils/map.ts +377 -0
  177. package/src/utils/number.ts +101 -0
  178. package/src/utils/page.ts +81 -0
  179. package/src/utils/str.ts +26 -0
  180. package/src/utils/tools.ts +44 -0
  181. package/src/utils/tree.ts +145 -0
  182. package/src/utils/url.ts +40 -0
  183. package/src/utils/util.ts +99 -0
  184. package/src/utils/workbench.ts +25 -0
  185. package/tsconfig.json +33 -0
@@ -0,0 +1,97 @@
1
+ import { styled, keyframes, css } from 'styled-components';
2
+ import $i18n from 'panda-i18n';
3
+
4
+ const isChinese = $i18n.getLang() === 'zh-CN';
5
+
6
+ const slideInFromRight = keyframes`
7
+ from {
8
+ transform: translateX(100%);
9
+ opacity: 0;
10
+ filter: blur(3px); /* 开始时模糊 */
11
+ }
12
+ to {
13
+ transform: translateX(0);
14
+ opacity: 1;
15
+ filter: blur(0); /* 结束时清晰 */
16
+ }
17
+ `;
18
+
19
+ interface IProps {
20
+ onClick: () => void;
21
+ }
22
+
23
+ export const UnreadRobot = (args: IProps) => {
24
+ return (
25
+ <Wrapper {...args}>
26
+ <ChangeTipImg
27
+ src={
28
+ isChinese
29
+ ? 'https://img.alicdn.com/imgextra/i1/O1CN01W657Of1iK2qUG0WKS_!!6000000004393-1-tps-720-638.gif'
30
+ : 'https://img.alicdn.com/imgextra/i1/O1CN01KJmNjj1JlBvXcYJW4_!!6000000001068-1-tps-720-638.gif'
31
+ }
32
+ />
33
+ <RobotImg />
34
+ <ShadowImg />
35
+ </Wrapper>
36
+ );
37
+ };
38
+
39
+ export const ChangeRobot = (args: IProps) => {
40
+ return (
41
+ <Wrapper {...args}>
42
+ <ChangeTipImg
43
+ src={
44
+ isChinese
45
+ ? 'https://img.alicdn.com/imgextra/i3/O1CN01rdpqie1Gi2LsgNFff_!!6000000000655-1-tps-720-638.gif'
46
+ : 'https://img.alicdn.com/imgextra/i4/O1CN018SJdhy1DbDbnjWYVE_!!6000000000234-1-tps-720-638.gif'
47
+ }
48
+ />
49
+ <RobotImg />
50
+ <ShadowImg />
51
+ </Wrapper>
52
+ );
53
+ };
54
+
55
+ const Wrapper = styled.div`
56
+ position: absolute;
57
+ width: 177px;
58
+ right: 0;
59
+ bottom: 80px;
60
+ cursor: pointer;
61
+ z-index: 1;
62
+ animation: ${slideInFromRight} 0.8s forwards;
63
+ `;
64
+
65
+ const ChangeTipImg = styled.img`
66
+ position: absolute;
67
+ width: 100%;
68
+ bottom: 0;
69
+ left: 0;
70
+ z-index: -1;
71
+
72
+ ${!isChinese &&
73
+ css`
74
+ bottom: 18px;
75
+ left: -50px;
76
+ `}
77
+ `;
78
+
79
+ const RobotImg = styled.img.attrs({
80
+ src: 'https://img.alicdn.com/imgextra/i4/O1CN01RXKj9z27ygNSyfxe7_!!6000000007866-1-tps-720-638.gif',
81
+ })`
82
+ position: absolute;
83
+ width: 100%;
84
+ bottom: 0;
85
+ left: 0;
86
+ `;
87
+
88
+ const ShadowImg = styled.img.attrs({
89
+ src: 'https://img.alicdn.com/imgextra/i3/O1CN01vLugxW1rYXvF8V5XP_!!6000000005643-2-tps-720-638.png',
90
+ })`
91
+ position: absolute;
92
+ width: 80%;
93
+ bottom: 0;
94
+ left: 21px;
95
+ z-index: -1;
96
+ opacity: 0.5;
97
+ `;
@@ -0,0 +1,102 @@
1
+ import { useVDialogValue } from '../hooks/useVDialogState';
2
+ import { useEventListener } from 'ahooks';
3
+ import { useRef, useState, useEffect } from 'react';
4
+ import { useSetVRemindMsgShow } from './useVRemindMsgShow';
5
+ import { useVDialogOperate } from './useVDialogOperate';
6
+
7
+ export const useVDialogCore = () => {
8
+ const dialogState = useVDialogValue();
9
+ const [isInputFocus, setIsInputFocus] = useState(false);
10
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
11
+ const setVRemindMsgShow = useSetVRemindMsgShow();
12
+ const lastScrollTopRef = useRef(0);
13
+
14
+ const {
15
+ handleInputChange,
16
+ handleSend,
17
+ handleQueryDialogList,
18
+ handleUpdateDialogMsg,
19
+ } = useVDialogOperate();
20
+
21
+ // 处理聚焦
22
+ const handleFocus = (isFocus: boolean) => {
23
+ setIsInputFocus(isFocus);
24
+ };
25
+
26
+ // 向上滚动隐患提醒消息
27
+ useEffect(() => {
28
+ const container = scrollContainerRef.current;
29
+ if (container) {
30
+ const onScroll = (e: Event) => {
31
+ const currentScrollTop = container.scrollTop;
32
+ const lastScrollTop = lastScrollTopRef.current;
33
+
34
+ if (currentScrollTop < lastScrollTop) {
35
+ // 向上滚动
36
+ setVRemindMsgShow(false);
37
+ }
38
+
39
+ // 更新最后的滚动位置
40
+ lastScrollTopRef.current = currentScrollTop;
41
+ };
42
+
43
+ container.addEventListener('scroll', onScroll);
44
+
45
+ // 组件卸载时移除滚动监听
46
+ return () => {
47
+ if (container) {
48
+ container.removeEventListener('scroll', onScroll);
49
+ }
50
+ };
51
+ }
52
+ }, [setVRemindMsgShow]);
53
+
54
+ // 聚集输入框时,隐藏提醒消息
55
+ useEffect(() => {
56
+ if (isInputFocus) {
57
+ setVRemindMsgShow(false);
58
+ }
59
+ }, [isInputFocus, setVRemindMsgShow]);
60
+
61
+ // 监听回车键
62
+ useEventListener('keydown', (e) => {
63
+ if (e.code === 'Enter' && isInputFocus) {
64
+ handleSend(dialogState.input);
65
+ }
66
+ });
67
+
68
+ // 触发滚动到底部
69
+ useEffect(() => {
70
+ const container = scrollContainerRef.current;
71
+
72
+ setTimeout(() => {
73
+ if (container) {
74
+ container.scrollTop = container.scrollHeight;
75
+ }
76
+ }, 0);
77
+
78
+ if (container) {
79
+ // 使用MutationObserver来观察DOM变化
80
+ const observer = new MutationObserver(() => {
81
+ container.scrollTop = container.scrollHeight;
82
+ observer.disconnect();
83
+ });
84
+
85
+ // 仅观察子节点变动,根据需要可以调整配置
86
+ observer.observe(container, { childList: true, subtree: true });
87
+
88
+ // 在组件卸载时断开观察
89
+ return () => observer.disconnect();
90
+ }
91
+ }, [dialogState.scrollToBottomTag]);
92
+
93
+ return {
94
+ scrollContainerRef,
95
+ dialogState,
96
+ handleSend,
97
+ handleInputChange,
98
+ handleFocus,
99
+ handleQueryDialogList,
100
+ handleUpdateDialogMsg,
101
+ };
102
+ };
@@ -0,0 +1,132 @@
1
+ import { useSetVDialogState } from '../hooks/useVDialogState';
2
+ import { DialogService } from '@/services/DialogService';
3
+ import { mergeAndDeduplicateArrays } from '@/utils';
4
+ import { CnMessage } from '@cainiaofe/cn-ui';
5
+ import { useCallback } from 'react';
6
+ import { IDialogMsgInfo } from '@/types';
7
+
8
+ export const useVDialogOperate = () => {
9
+ const setDialogState = useSetVDialogState();
10
+
11
+ // 查询对话
12
+ const handleQueryDialogList = useCallback(
13
+ async (currentPage = 1) => {
14
+ try {
15
+ const data = await DialogService.queryList({
16
+ currentPage,
17
+ pageSize: 10,
18
+ });
19
+
20
+ setDialogState((state) => ({
21
+ ...state,
22
+ dialogList: mergeAndDeduplicateArrays(
23
+ state.dialogList,
24
+ data.tableData,
25
+ ),
26
+ paging: data.paging,
27
+ ...(currentPage === 1 && {
28
+ scrollToBottomTag: state.scrollToBottomTag + 1,
29
+ }),
30
+ }));
31
+ } catch (error) {
32
+ CnMessage.error(error.message);
33
+ }
34
+ },
35
+ [setDialogState],
36
+ );
37
+
38
+ // 发送
39
+ const handleSend = useCallback(
40
+ async (msg?: string) => {
41
+ const sendMsg = msg?.trim();
42
+
43
+ if (!sendMsg) {
44
+ return;
45
+ }
46
+
47
+ try {
48
+ const regex = /^\/问题反馈\s+(.+)/;
49
+ const match = sendMsg.match(regex);
50
+ if (match) {
51
+ // 问题反馈
52
+ setDialogState((state) => ({
53
+ ...state,
54
+ loading: true,
55
+ input: '',
56
+ scrollToBottomTag: state.scrollToBottomTag + 1,
57
+ }));
58
+ const feedbackContent = match[1];
59
+ await DialogService.feedback(feedbackContent);
60
+ CnMessage.success('问题反馈成功');
61
+ return;
62
+ }
63
+
64
+ // 发送消息
65
+ setDialogState((state) => ({
66
+ ...state,
67
+ loading: true,
68
+ input: '',
69
+ dialogList: [
70
+ ...state.dialogList,
71
+ { id: -1, content: sendMsg },
72
+ { id: -2, sender: '0' },
73
+ ],
74
+ scrollToBottomTag: state.scrollToBottomTag + 1,
75
+ }));
76
+ await DialogService.send(sendMsg);
77
+ handleQueryDialogList();
78
+ } catch (error) {
79
+ CnMessage.error(error.message);
80
+ } finally {
81
+ setDialogState((state) => ({
82
+ ...state,
83
+ loading: false,
84
+ }));
85
+ }
86
+ },
87
+ [handleQueryDialogList, setDialogState],
88
+ );
89
+
90
+ // 更新信息
91
+ const handleUpdateDialogMsg = useCallback(
92
+ async (data: Partial<IDialogMsgInfo>) => {
93
+ if (typeof data.id === 'number') {
94
+ try {
95
+ // 1. 更远程状态
96
+ await DialogService.update(data);
97
+
98
+ // 2. 更新本地状态
99
+ setDialogState((state) => ({
100
+ ...state,
101
+ dialogList: state.dialogList.map((item) => ({
102
+ ...item,
103
+ ...(item.id === data.id && data),
104
+ })),
105
+ }));
106
+ } catch (error) {
107
+ CnMessage.error(error.message);
108
+ }
109
+ }
110
+ },
111
+ [setDialogState],
112
+ );
113
+
114
+ // 处理输入
115
+ const handleInputChange = useCallback(
116
+ (input: string) => {
117
+ setDialogState((state) => ({
118
+ ...state,
119
+ input,
120
+ }));
121
+ },
122
+ [setDialogState],
123
+ );
124
+
125
+ return {
126
+ handleSetDialogState: setDialogState,
127
+ handleInputChange,
128
+ handleSend,
129
+ handleQueryDialogList,
130
+ handleUpdateDialogMsg,
131
+ };
132
+ };
@@ -0,0 +1,52 @@
1
+ import { ICnPaging, IDialogMsgInfo } from '@/types';
2
+ import { atom, selector, useRecoilValue, useSetRecoilState } from 'recoil';
3
+
4
+ interface IVDialogState {
5
+ input: string;
6
+ loading: boolean;
7
+ show: boolean;
8
+ dialogList: IDialogMsgInfo[];
9
+ paging: ICnPaging;
10
+
11
+ /**
12
+ * 滚动到底部标记
13
+ */
14
+ scrollToBottomTag: number;
15
+ }
16
+
17
+ const vDialogState = atom<IVDialogState>({
18
+ key: 'v-dialog-state',
19
+ default: {
20
+ input: '',
21
+ loading: false,
22
+ show: true,
23
+ dialogList: [],
24
+ paging: {
25
+ currentPage: 1,
26
+ pageSize: 10,
27
+ totalCount: 0,
28
+ },
29
+ scrollToBottomTag: 0,
30
+ },
31
+ });
32
+
33
+ export const useVDialogValue = () => useRecoilValue(vDialogState);
34
+ export const useSetVDialogState = () => useSetRecoilState(vDialogState);
35
+
36
+ const hasChangeMsgState = selector({
37
+ key: 'has-change-msg-state',
38
+ get: ({ get }) => {
39
+ const { dialogList } = get(vDialogState);
40
+ return dialogList.some(
41
+ (item) => item.type === 'text_multiply' && item.status === 0,
42
+ );
43
+ },
44
+ });
45
+
46
+ /**
47
+ * 是否有未读变更消息
48
+ * @returns
49
+ */
50
+ export const useHasChangeMsg = () => {
51
+ return useRecoilValue(hasChangeMsgState);
52
+ };
@@ -0,0 +1,15 @@
1
+ import {
2
+ atom,
3
+ useRecoilState,
4
+ useRecoilValue,
5
+ useSetRecoilState,
6
+ } from 'recoil';
7
+
8
+ const state = atom({
9
+ key: 'v-remind-msg-show',
10
+ default: true,
11
+ });
12
+
13
+ export const useVRemindMsgShow = () => useRecoilValue(state);
14
+ export const useVRemindMsgShowState = () => useRecoilState(state);
15
+ export const useSetVRemindMsgShow = () => useSetRecoilState(state);
@@ -0,0 +1,205 @@
1
+ import $i18n from 'panda-i18n';
2
+ import { CnMessage } from '@cainiaofe/cn-ui';
3
+ import { PageService } from '@/services/PageService';
4
+ import { IUnreadMsg } from '@/types';
5
+ import { css, styled } from 'styled-components';
6
+ import { VRemindMsgList } from './components/VRemindMsgList';
7
+ import { VDialog } from './components/VDialog';
8
+ import { useEffect, useMemo, useRef, useState } from 'react';
9
+ import { useVRemindMsgShowState } from './hooks/useVRemindMsgShow';
10
+ import { rgba } from 'polished';
11
+ import { RESPONSIVE_BREAKPOINT } from '@/utils';
12
+ import { useSetVDialogState } from './hooks/useVDialogState';
13
+ import { CustomIcon } from '../icon';
14
+
15
+ interface IProps {
16
+ msgList: IUnreadMsg[];
17
+ handleRefreshMsgList: () => void;
18
+ }
19
+
20
+ export function VShowPanel({ msgList, handleRefreshMsgList }: IProps) {
21
+ const setVDialogState = useSetVDialogState();
22
+ const bizTypeList = useMemo(
23
+ () => [...new Set<string>([...msgList.map((m) => m.bizType2)])],
24
+ [msgList],
25
+ );
26
+
27
+ // 标记已读并刷新数据
28
+ const handleRefreshData = async (id: number | number[]) => {
29
+ try {
30
+ if (Array.isArray(id)) {
31
+ await PageService.markUnreadMsgList(id);
32
+ } else {
33
+ await PageService.markUnreadMsg(id);
34
+ }
35
+ } catch (error) {
36
+ CnMessage.error(error.message);
37
+ }
38
+ handleRefreshMsgList();
39
+ };
40
+
41
+ const [vRemindMsgState, setVRemindMsgState] = useVRemindMsgShowState();
42
+
43
+ const magGroupListRef = useRef<HTMLDivElement>(null);
44
+ const [height, setHeight] = useState(0);
45
+
46
+ useEffect(() => {
47
+ if (vRemindMsgState) {
48
+ setHeight(magGroupListRef.current?.clientHeight || 0);
49
+ } else {
50
+ setHeight(0);
51
+ }
52
+ }, [vRemindMsgState]);
53
+
54
+ useEffect(() => {
55
+ if (msgList.length === 0) {
56
+ setVDialogState((state) => ({
57
+ ...state,
58
+ show: true,
59
+ }));
60
+ }
61
+ }, [msgList.length, setVDialogState]);
62
+
63
+ return (
64
+ <Wrapper>
65
+ <VRobot />
66
+ <MsgAreaWrapper>
67
+ <div data-title>
68
+ {$i18n.get({ id: 'SmallVAssistant', dm: '小V助手' })}
69
+ </div>
70
+ <div data-description>
71
+ {msgList.length === 0 ? (
72
+ <span>
73
+ {$i18n.get({
74
+ id: 'DearYourMessageBoxIsNeat',
75
+ dm: '亲,您的消息盒子很整洁。',
76
+ })}
77
+ </span>
78
+ ) : (
79
+ <span>
80
+ <span>
81
+ {$i18n.get({ id: 'DearYouHave', dm: '亲,您有' })}
82
+ <span
83
+ data-open
84
+ onClick={() => {
85
+ setVRemindMsgState(true);
86
+ }}
87
+ >
88
+ {` ${msgList.length} `}
89
+ </span>
90
+ {$i18n.get({ id: 'UnreadMessages', dm: '条未读消息' })}
91
+ </span>
92
+ <CustomIcon
93
+ type="icon-yijianqingli"
94
+ size={16}
95
+ onClick={() => {
96
+ handleRefreshData(msgList.map((item) => item.id));
97
+ setVRemindMsgState(false);
98
+ }}
99
+ style={{
100
+ marginLeft: 16,
101
+ cursor: 'pointer',
102
+ transform: 'translateY(1px)',
103
+ }}
104
+ />
105
+ </span>
106
+ )}
107
+ </div>
108
+ </MsgAreaWrapper>
109
+ <MsgGroupListWrapper $show={vRemindMsgState} ref={magGroupListRef}>
110
+ {bizTypeList.map((item) => (
111
+ <VRemindMsgList
112
+ key={item}
113
+ data={msgList.filter((msg) => msg.bizType2 === item)}
114
+ onRefreshData={handleRefreshData}
115
+ />
116
+ ))}
117
+ </MsgGroupListWrapper>
118
+ <DialogAreaWrapper style={{ paddingTop: height ? height - 8 : 0 }}>
119
+ <VDialog style={{ background: 'transparent', padding: 0 }} />
120
+ </DialogAreaWrapper>
121
+ </Wrapper>
122
+ );
123
+ }
124
+
125
+ const TOP_HEIGHT = '80px';
126
+
127
+ const Wrapper = styled.div`
128
+ width: 100%;
129
+ height: 100%;
130
+ padding: 20px;
131
+ position: relative;
132
+ `;
133
+
134
+ const MsgAreaWrapper = styled.div`
135
+ height: ${TOP_HEIGHT};
136
+ padding-bottom: 12px;
137
+
138
+ [data-title] {
139
+ font-size: 20px;
140
+ color: #222;
141
+ line-height: 1;
142
+ margin-bottom: 8px;
143
+ }
144
+
145
+ [data-description] {
146
+ width: max-content;
147
+ font-size: 12px;
148
+ color: #272e3a;
149
+ padding: 8px 12px;
150
+ border-radius: 12px 0px 12px 12px;
151
+ padding-right: 50px;
152
+ background: ${rgba('#3399FF', 0.1)};
153
+
154
+ [data-open] {
155
+ color: #3d84ff;
156
+ cursor: pointer;
157
+ }
158
+ }
159
+ `;
160
+
161
+ const DialogAreaWrapper = styled.div`
162
+ height: calc(100% - ${TOP_HEIGHT});
163
+ `;
164
+
165
+ export const MsgGroupListWrapper = styled.div<{ $show?: boolean }>`
166
+ display: grid;
167
+ grid: auto-flow auto / 1fr;
168
+ gap: 16px;
169
+ place-content: start;
170
+ padding-bottom: 20px;
171
+ width: 100%;
172
+ max-height: 85%;
173
+ padding: 0 20px;
174
+ overflow: auto;
175
+ position: absolute;
176
+ left: 0;
177
+ top: 92px;
178
+ padding-bottom: 20px;
179
+
180
+ &::-webkit-scrollbar {
181
+ display: none;
182
+ }
183
+
184
+ ${(props) =>
185
+ !props.$show &&
186
+ css`
187
+ visibility: hidden;
188
+ `}
189
+ `;
190
+
191
+ export const VRobot = styled.div`
192
+ width: 170px;
193
+ height: 160px;
194
+ background: url('https://img.alicdn.com/imgextra/i1/O1CN01Zzbxyo1M7kf8Dx46V_!!6000000001388-2-tps-700-636.png')
195
+ no-repeat center / cover;
196
+ position: absolute;
197
+ right: 0px;
198
+ top: -4px;
199
+
200
+ @media (max-width: ${RESPONSIVE_BREAKPOINT}) {
201
+ width: 3.6rem;
202
+ height: 3.4rem;
203
+ right: -0.1rem;
204
+ }
205
+ `;
@@ -0,0 +1,103 @@
1
+ import { styled, css } from 'styled-components';
2
+
3
+ export const CardWrapper = styled.div<{ $hidden?: boolean }>`
4
+ ${(props) =>
5
+ props.$hidden &&
6
+ css`
7
+ display: none;
8
+ `}
9
+
10
+ width: 100%;
11
+ height: 100%;
12
+ position: relative;
13
+ background-color: #fff;
14
+ padding: 20px;
15
+ border-radius: 12px;
16
+ box-shadow: 0 2px 4px 0px rgba(39, 46, 58, 0.12);
17
+ background: rgba(255, 255, 255, 0.8);
18
+ backdrop-filter: blur(30px);
19
+
20
+ ${(props) => css`
21
+ background-color: ${props.theme.cardBackgroundColor};
22
+ `};
23
+
24
+ .cn-next-loading-wrap {
25
+ width: 100% !important;
26
+ height: 100% !important;
27
+ }
28
+ `;
29
+
30
+ export const CardWrapperMobile = styled.div<{ $show?: boolean }>`
31
+ ${(props) =>
32
+ props.$show &&
33
+ css`
34
+ display: none;
35
+ `}
36
+
37
+ width: 100%;
38
+ height: 100%;
39
+ position: relative;
40
+ padding: 0.32rem;
41
+ border-radius: 0.24rem;
42
+ box-shadow: 0 0.02rem 0.04rem 0 rgba(39, 46, 58, 0.12);
43
+ background: rgba(255, 255, 255, 0.8);
44
+ backdrop-filter: blur(30px);
45
+
46
+ ${(props) => css`
47
+ background-color: ${props.theme.cardBackgroundColor};
48
+ `};
49
+
50
+ .cn-next-loading-wrap {
51
+ width: 100% !important;
52
+ height: 100% !important;
53
+ }
54
+ `;
55
+
56
+ export const CardTitleWrapper = styled.div<{ $checked?: boolean }>`
57
+ color: ${(props) => props.theme.color ?? '#000'};
58
+ font-size: 14px;
59
+ font-weight: 700;
60
+ line-height: 1;
61
+ margin-bottom: 8px;
62
+ ${({ $checked = true }) =>
63
+ !$checked &&
64
+ css`
65
+ color: ${(props) => props.theme.color ?? '#6d7a90'};
66
+ `}
67
+ `;
68
+
69
+ export const CardTitleWrapperMobile = styled.div<{ $checked?: boolean }>`
70
+ color: ${(props) => props.theme.color ?? '#272F3D'};
71
+ font-weight: 600;
72
+ font-size: 0.32rem;
73
+ line-height: 1;
74
+ margin-bottom: 0.2rem;
75
+
76
+ ${({ $checked = true }) =>
77
+ !$checked &&
78
+ css`
79
+ color: ${(props) => props.theme.color ?? '#6d7a90'};
80
+ `}
81
+ `;
82
+
83
+ export const IconWrapper = styled.div<{ $src?: string }>`
84
+ width: 30px;
85
+ height: 30px;
86
+ border-radius: 8px;
87
+ padding: 8px;
88
+ box-shadow: 0 2px 4px 0px rgba(39, 46, 58, 0.12);
89
+ background: no-repeat center / 16px 16px url('${(props) => props.$src}');
90
+ cursor: pointer;
91
+ background-color: #fff;
92
+
93
+ ${(props) => css`
94
+ background-color: ${props.theme.iconBackgroundColor};
95
+ `}
96
+ `;
97
+
98
+ export const UnitWrapper = styled.span`
99
+ color: #6d7a90;
100
+ font-size: 12px;
101
+ padding-left: 5px;
102
+ display: inline-block;
103
+ `;