@fe-free/core 2.2.7 → 2.2.9

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @fe-free/core
2
2
 
3
+ ## 2.2.9
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @fe-free/tool@2.2.9
9
+
10
+ ## 2.2.8
11
+
12
+ ### Patch Changes
13
+
14
+ - feat: global
15
+ - @fe-free/tool@2.2.8
16
+
3
17
  ## 2.2.7
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fe-free/core",
3
- "version": "2.2.7",
3
+ "version": "2.2.9",
4
4
  "description": "",
5
5
  "main": "./src/index.ts",
6
6
  "author": "",
@@ -28,7 +28,9 @@
28
28
  "ahooks": "^3.7.8",
29
29
  "axios": "^1.6.5",
30
30
  "classnames": "^2.5.1",
31
+ "file-saver": "^2.0.5",
31
32
  "github-markdown-css": "^5.8.1",
33
+ "i": "^0.3.7",
32
34
  "localforage": "^1.10.0",
33
35
  "lodash-es": "^4.17.21",
34
36
  "react-ace": "^11.0.1",
@@ -39,7 +41,7 @@
39
41
  "remark-gfm": "^4.0.1",
40
42
  "vanilla-jsoneditor": "^0.23.1",
41
43
  "zustand": "^4.5.4",
42
- "@fe-free/tool": "2.2.7"
44
+ "@fe-free/tool": "2.2.9"
43
45
  },
44
46
  "peerDependencies": {
45
47
  "@ant-design/pro-components": "2.8.9",
@@ -0,0 +1,78 @@
1
+ import { useGlobalRequest } from '@fe-free/core';
2
+ import { useRequest } from 'ahooks';
3
+
4
+ const meta = {
5
+ title: '@fe-free/core/ahooks',
6
+ tags: ['autodocs'],
7
+ parameters: {
8
+ docs: {
9
+ description: {
10
+ component: `基于 ahooks 封装
11
+ - useGlobalRequest。基于 useRequest 封装,会抛出全局错误。
12
+ - useGlobalInfiniteScroll。基于 useInfiniteScroll 封装,会抛出全局错误。
13
+ `,
14
+ },
15
+ },
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+
21
+ const handleError = (event) => {
22
+ console.log('global error', event);
23
+ alert('global error');
24
+ };
25
+
26
+ window.addEventListener('error', handleError);
27
+ window.addEventListener('unhandledrejection', handleError);
28
+
29
+ export const UseGlobalRequest = () => {
30
+ const { data, loading, error, run } = useGlobalRequest(
31
+ async () => {
32
+ return new Promise((_, reject) => {
33
+ setTimeout(() => {
34
+ reject('错误啦');
35
+ }, 1000);
36
+ });
37
+ },
38
+ {
39
+ manual: true,
40
+ },
41
+ );
42
+
43
+ return (
44
+ <div>
45
+ <div>useGlobalRequest 会抛出全局错误</div>
46
+ <button onClick={run}>run</button>
47
+
48
+ <div>data: {data}</div>
49
+ <div>loading: {loading ? 'true' : 'false'}</div>
50
+ <div>error: {error}</div>
51
+ </div>
52
+ );
53
+ };
54
+
55
+ export const UseRequest = () => {
56
+ const { data, loading, error, run } = useRequest(
57
+ async () => {
58
+ return new Promise((_, reject) => {
59
+ setTimeout(() => {
60
+ reject('错误啦');
61
+ }, 1000);
62
+ });
63
+ },
64
+ {
65
+ manual: true,
66
+ },
67
+ );
68
+
69
+ return (
70
+ <div>
71
+ <div>ahooks useRequest</div>
72
+ <button onClick={run}>run</button>
73
+ <div>data: {data}</div>
74
+ <div>loading: {loading ? 'true' : 'false'}</div>
75
+ <div>error: {error}</div>
76
+ </div>
77
+ );
78
+ };
@@ -8,8 +8,7 @@ const meta: Meta<typeof LoadingButton> = {
8
8
  parameters: {
9
9
  docs: {
10
10
  description: {
11
- component:
12
- 'LoadingButton 是一个带有加载状态的按钮组件,适用于异步操作场景。<br/>区别于 antd Button 需要手动传 loading props。',
11
+ component: '基于 antd Button 封装的 LoadingButton,自动根据 onClick 显示 loading',
13
12
  },
14
13
  },
15
14
  },
@@ -2,7 +2,9 @@ import type { ButtonProps } from 'antd';
2
2
  import { Button } from 'antd';
3
3
  import { useCallback, useState } from 'react';
4
4
 
5
- function LoadingButton({ onClick, ...rest }: ButtonProps) {
5
+ function LoadingButton(props: ButtonProps) {
6
+ const { onClick, ...rest } = props;
7
+
6
8
  const [loading, setLoading] = useState(false);
7
9
 
8
10
  const handleClick = useCallback(
@@ -5,6 +5,13 @@ const meta: Meta<typeof Copy> = {
5
5
  title: '@fe-free/core/Copy',
6
6
  component: Copy,
7
7
  tags: ['autodocs'],
8
+ parameters: {
9
+ docs: {
10
+ description: {
11
+ component: '基于 antd 的 Copy 组件,自动根据 value 显示复制按钮',
12
+ },
13
+ },
14
+ },
8
15
  };
9
16
 
10
17
  export default meta;
@@ -14,7 +21,7 @@ type Story = StoryObj<typeof meta>;
14
21
  export const Basic: Story = {
15
22
  args: {
16
23
  value: '点击复制',
17
- children: '点击复制',
24
+ children: 'children 点击复制',
18
25
  },
19
26
  };
20
27
 
@@ -22,7 +29,7 @@ export const ShowIcon: Story = {
22
29
  args: {
23
30
  value: 'icon复制',
24
31
  showIcon: true,
25
- children: 'icon复制',
32
+ children: 'children 点击复制',
26
33
  },
27
34
  };
28
35
 
@@ -31,7 +38,7 @@ export const HoverIcon: Story = {
31
38
  value: 'hover复制',
32
39
  showIcon: true,
33
40
  hoverIcon: true,
34
- children: 'hover复制',
41
+ children: 'children 点击复制',
35
42
  },
36
43
  };
37
44
 
@@ -41,6 +48,6 @@ export const OnCopied: Story = {
41
48
  onCopied: () => {
42
49
  alert('复制成功');
43
50
  },
44
- children: '点击复制',
51
+ children: 'children 点击复制',
45
52
  },
46
53
  };
@@ -4,14 +4,20 @@ import classNames from 'classnames';
4
4
  import React, { useCallback } from 'react';
5
5
 
6
6
  interface CopyProps {
7
+ /** 复制的内容 */
7
8
  value: string;
9
+ /** 是否显示复制 icon */
8
10
  showIcon?: boolean;
11
+ /** 是否在 hover 时显示复制 icon */
9
12
  hoverIcon?: boolean;
10
13
  children?: React.ReactNode;
14
+ /** 复制成功后的回调 */
11
15
  onCopied?: () => void;
12
16
  }
13
17
 
14
- const Copy: React.FC<CopyProps> = ({ value, showIcon, hoverIcon, children, onCopied }) => {
18
+ function Copy(props: CopyProps) {
19
+ const { value, showIcon, hoverIcon, children, onCopied } = props;
20
+
15
21
  const handleCopy = useCallback(async () => {
16
22
  await copyToClipboard(value);
17
23
  onCopied?.();
@@ -39,7 +45,7 @@ const Copy: React.FC<CopyProps> = ({ value, showIcon, hoverIcon, children, onCop
39
45
  }
40
46
 
41
47
  return <div onClick={handleCopy}>{children}</div>;
42
- };
48
+ }
43
49
 
44
50
  export { Copy };
45
51
  export type { CopyProps };
@@ -0,0 +1,89 @@
1
+ import { message } from 'antd';
2
+ import type { AxiosRequestConfig, AxiosResponse } from 'axios';
3
+ import { AxiosError } from 'axios';
4
+
5
+ class RequestError extends Error {
6
+ silent: boolean | undefined;
7
+ status: string | undefined;
8
+ config: AxiosRequestConfig<any> | undefined;
9
+ request: XMLHttpRequest | undefined;
10
+ response: AxiosResponse<any, any> | undefined;
11
+
12
+ constructor(
13
+ message: string,
14
+ {
15
+ silent,
16
+ config,
17
+ request,
18
+ response,
19
+ }: {
20
+ silent?: boolean;
21
+ status?: number;
22
+ config?: AxiosRequestConfig;
23
+ request?: XMLHttpRequest;
24
+ response?: AxiosResponse;
25
+ },
26
+ ) {
27
+ super(message);
28
+ this.name = 'RequestError';
29
+
30
+ this.silent = silent;
31
+ this.status = status;
32
+ this.config = config;
33
+ this.request = request;
34
+ this.response = response;
35
+ }
36
+ }
37
+
38
+ function commonHandleError(event) {
39
+ console.log('handleError', event);
40
+
41
+ if (event.reason) {
42
+ if (event.reason instanceof AxiosError) {
43
+ if (event.reason.code === 'ERR_NETWORK') {
44
+ message.error('网络异常');
45
+ return;
46
+ }
47
+ if (event.reason.code === 'ECONNABORTED') {
48
+ message.error('请求超时');
49
+ return;
50
+ }
51
+
52
+ if (event.reason.code === 'ERR_CANCELED') {
53
+ // 正常逻辑
54
+ return;
55
+ }
56
+ }
57
+
58
+ if (event.reason.message || event.reason.reason) {
59
+ message.error(event.reason.message || event.reason.reason);
60
+ return;
61
+ }
62
+ }
63
+
64
+ if (event.error) {
65
+ if (event.error.message || event.error.reason) {
66
+ message.error(event.error.message || event.error.reason);
67
+ return;
68
+ }
69
+ }
70
+
71
+ // unknown error
72
+ }
73
+
74
+ function initErrorHandle(onError) {
75
+ const handleError = (event) => {
76
+ commonHandleError(event);
77
+ onError?.(event);
78
+ };
79
+
80
+ window.addEventListener('error', handleError);
81
+ window.addEventListener('unhandledrejection', handleError);
82
+
83
+ return () => {
84
+ window.removeEventListener('error', handleError);
85
+ window.removeEventListener('unhandledrejection', handleError);
86
+ };
87
+ }
88
+
89
+ export { initErrorHandle, RequestError };
@@ -0,0 +1,34 @@
1
+ import type { AxiosInstance } from 'axios';
2
+ import { saveAs } from 'file-saver';
3
+
4
+ function downloadInterceptor(instance: AxiosInstance) {
5
+ instance.interceptors.response.use(async function (response) {
6
+ const contentDisposition = response.headers['content-disposition'];
7
+
8
+ if (contentDisposition) {
9
+ let filename;
10
+
11
+ // 更加健壮且简洁的写法,优先处理 filename*=,否则处理 filename=
12
+ if (contentDisposition.includes('filename*=')) {
13
+ // RFC 5987 格式:filename*=utf-8''xxx
14
+ const match = contentDisposition.match(/filename\*\s*=\s*([^']*)''([^;]+)/i);
15
+ filename = match ? decodeURIComponent(match[2]) : undefined;
16
+ } else if (contentDisposition.includes('filename=')) {
17
+ // 普通格式:filename=xxx
18
+ const match = contentDisposition.match(/filename\s*=\s*("?)([^";]+)\1/i);
19
+ filename = match ? decodeURIComponent(match[2]) : undefined;
20
+ }
21
+
22
+ filename = filename || 'download';
23
+
24
+ // 处理文件下载 - 确保正确处理二进制数据
25
+ const blob = response.data;
26
+
27
+ saveAs(blob, filename);
28
+ }
29
+
30
+ return response;
31
+ });
32
+ }
33
+
34
+ export { downloadInterceptor };
package/src/index.ts CHANGED
@@ -30,6 +30,8 @@ export {
30
30
  ProFormUploadDragger,
31
31
  proFormSelectSearchProps,
32
32
  } from './form';
33
+ export { RequestError, initErrorHandle } from './global/error';
34
+ export { downloadInterceptor } from './global/interceptors';
33
35
  export { Markdown } from './markdown';
34
36
  export { PageLayout } from './page_layout';
35
37
  export { routeTool } from './route';
@@ -7,10 +7,10 @@ export const Default = () => {
7
7
  return (
8
8
  <div>
9
9
  <div>@fe-free/core 扩展的</div>
10
- <div className="flex flex-row gap-2">
11
- <span className="text-primary">text-primary</span>
12
- <span className="text-secondary">text-secondary</span>
13
- <span className="text-desc">text-desc</span>
10
+ <div>
11
+ <div className="text-primary">text-primary</div>
12
+ <div className="text-secondary">text-secondary</div>
13
+ <div className="text-desc">text-desc</div>
14
14
  </div>
15
15
  <div>
16
16
  <div className="c-border-bottom">c-border-bottom</div>