@quansitech/antd-admin 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.
- package/components/Column/Cascader.tsx +79 -0
- package/components/Column/File.tsx +168 -0
- package/components/Column/Image.tsx +77 -0
- package/components/Column/Readonly/Cascader.tsx +51 -0
- package/components/Column/Readonly/File.tsx +54 -0
- package/components/Column/Readonly/Image.tsx +78 -0
- package/components/Column/Readonly/Option.tsx +58 -0
- package/components/Column/Readonly/types.d.ts +9 -0
- package/components/Column/Ueditor.tsx +314 -0
- package/components/Column/types.d.ts +29 -0
- package/components/Form/Action/Button.tsx +125 -0
- package/components/Form/Action/types.d.ts +5 -0
- package/components/Form/Actions.tsx +35 -0
- package/components/Form.tsx +171 -0
- package/components/FormContext.ts +8 -0
- package/components/Layout.tsx +237 -0
- package/components/LayoutContext.ts +26 -0
- package/components/ModalContext.ts +16 -0
- package/components/Table/Action/Button.tsx +89 -0
- package/components/Table/Action/StartEditable.tsx +59 -0
- package/components/Table/Action/types.d.ts +7 -0
- package/components/Table/Option/Link.tsx +68 -0
- package/components/Table/Option/types.d.ts +5 -0
- package/components/Table/ToolbarActions.tsx +39 -0
- package/components/Table.scss +7 -0
- package/components/Table.tsx +279 -0
- package/components/TableContext.ts +14 -0
- package/components/Tabs.tsx +72 -0
- package/components/types.d.ts +0 -0
- package/lib/container.ts +82 -0
- package/lib/customRule.ts +10 -0
- package/lib/global.ts +11 -0
- package/lib/helpers.tsx +150 -0
- package/lib/http.ts +74 -0
- package/lib/schemaHandler.ts +122 -0
- package/lib/upload.ts +177 -0
- package/package.json +35 -0
- package/readme.md +128 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import React, {lazy, Suspense, useContext, useEffect, useRef, useState} from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActionType,
|
|
4
|
+
FormInstance,
|
|
5
|
+
ProColumnType,
|
|
6
|
+
ProSkeleton,
|
|
7
|
+
ProTable,
|
|
8
|
+
ProTableProps
|
|
9
|
+
} from "@ant-design/pro-components";
|
|
10
|
+
import type {SortOrder} from "antd/lib/table/interface";
|
|
11
|
+
import {TablePaginationConfig} from "antd/es/table";
|
|
12
|
+
import isArray from "lodash/isArray"
|
|
13
|
+
import upperFirst from "lodash/upperFirst"
|
|
14
|
+
import {TableContext} from "./TableContext";
|
|
15
|
+
import ToolbarActions from "./Table/ToolbarActions";
|
|
16
|
+
import container from "../lib/container";
|
|
17
|
+
import {TableActionProps} from "./Table/Action/types";
|
|
18
|
+
import http from "../lib/http";
|
|
19
|
+
import {Spin} from "antd";
|
|
20
|
+
import "./Table.scss"
|
|
21
|
+
import {ModalContext} from "./ModalContext";
|
|
22
|
+
import cloneDeep from "lodash/cloneDeep";
|
|
23
|
+
import uniqueId from "lodash/uniqueId";
|
|
24
|
+
import {commonHandler} from "../lib/schemaHandler";
|
|
25
|
+
|
|
26
|
+
export type TableProps = ProTableProps<any, any> & {
|
|
27
|
+
columns: ProColumnType[],
|
|
28
|
+
dataSource: any[],
|
|
29
|
+
pagination: TablePaginationConfig & {
|
|
30
|
+
paramName?: string,
|
|
31
|
+
},
|
|
32
|
+
rowKey: string,
|
|
33
|
+
defaultSearchValue?: Record<string, any>,
|
|
34
|
+
actions: TableActionProps[],
|
|
35
|
+
searchUrl: string,
|
|
36
|
+
search?: boolean,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function (props: TableProps) {
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
const request = async (params: Record<string, any> & {
|
|
43
|
+
pageSize: number,
|
|
44
|
+
current: number
|
|
45
|
+
}, sort: Record<string, SortOrder>, filter: Record<string, (string | number)[] | null>) => {
|
|
46
|
+
setLoading(true)
|
|
47
|
+
const data: Record<string, any> = {
|
|
48
|
+
...params,
|
|
49
|
+
...filter,
|
|
50
|
+
sort,
|
|
51
|
+
}
|
|
52
|
+
if (props.pagination) {
|
|
53
|
+
data[props.pagination.paramName || 'page'] = data.current
|
|
54
|
+
delete data.current
|
|
55
|
+
delete data.pageSize
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setEditableKeys([])
|
|
59
|
+
setEditableValues([])
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const res = await http.get(props.searchUrl, {
|
|
63
|
+
params: data,
|
|
64
|
+
headers: {
|
|
65
|
+
'X-Table-Search': '1'
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (res.data.pagination) {
|
|
70
|
+
setPagination({
|
|
71
|
+
...res.data.pagination,
|
|
72
|
+
current: params.current,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
data: res.data.dataSource || [],
|
|
77
|
+
success: true,
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
setLoading(false)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const formRef = useRef<FormInstance>()
|
|
85
|
+
const actionRef = useRef<ActionType>()
|
|
86
|
+
const [editableKeys, setEditableKeys] = useState<React.Key[]>(() => [])
|
|
87
|
+
const [columns, setColumns] = useState<ProColumnType[]>([])
|
|
88
|
+
const [selectedRows, setSelectedRows] = useState<any[]>([])
|
|
89
|
+
const [editableValues, setEditableValues] = useState<Record<string, any>[]>([])
|
|
90
|
+
const [loading, setLoading] = useState(false)
|
|
91
|
+
const [initialized, setInitialized] = useState(false)
|
|
92
|
+
const [pagination, setPagination] = useState<TablePaginationConfig>()
|
|
93
|
+
const [dataSource, setDataSource] = useState<any[]>([])
|
|
94
|
+
const [sticky, setSticky] = useState<TableProps['sticky']>(true)
|
|
95
|
+
|
|
96
|
+
const modalContext = useContext(ModalContext)
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setPagination(props.pagination as TablePaginationConfig || false)
|
|
100
|
+
setDataSource(postData(props.dataSource))
|
|
101
|
+
|
|
102
|
+
// 重新定义列
|
|
103
|
+
setColumns(cloneDeep(props.columns)?.map((c: ProColumnType) => {
|
|
104
|
+
c.key = c.dataIndex as string
|
|
105
|
+
|
|
106
|
+
// 列render
|
|
107
|
+
const renderComponent = 'Column.Readonly.' + upperFirst(c.valueType as string)
|
|
108
|
+
if (container.check(renderComponent)) {
|
|
109
|
+
const Component = lazy(container.get(renderComponent))
|
|
110
|
+
c.render = (dom, record, _, action) =>
|
|
111
|
+
<Suspense fallback={<Spin/>}>
|
|
112
|
+
<Component {...c}
|
|
113
|
+
key={c.title as string}
|
|
114
|
+
record={record}
|
|
115
|
+
></Component>
|
|
116
|
+
</Suspense>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 列查询及编辑render
|
|
120
|
+
const formItemComponent = 'Column.' + upperFirst(c.valueType as string)
|
|
121
|
+
if (container.check(formItemComponent)) {
|
|
122
|
+
const Component = lazy(container.get(formItemComponent))
|
|
123
|
+
c.renderFormItem = (schema, config, form) => (
|
|
124
|
+
<Suspense fallback={<Spin/>}>
|
|
125
|
+
<Component config={config}
|
|
126
|
+
form={form}
|
|
127
|
+
schema={schema}
|
|
128
|
+
fieldProps={c.fieldProps}
|
|
129
|
+
key={c.title as string}
|
|
130
|
+
></Component>
|
|
131
|
+
</Suspense>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (props.defaultSearchValue?.[c.dataIndex as string] !== undefined) {
|
|
136
|
+
c.initialValue = props.defaultSearchValue[c.dataIndex as string]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
commonHandler(c)
|
|
140
|
+
if (container.schemaHandler[c.valueType as string]) {
|
|
141
|
+
return container.schemaHandler[c.valueType as string](c) as ProColumnType
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return c
|
|
145
|
+
}))
|
|
146
|
+
|
|
147
|
+
setLoading(false)
|
|
148
|
+
setInitialized(true)
|
|
149
|
+
|
|
150
|
+
if (!modalContext.inModal) {
|
|
151
|
+
setSticky({
|
|
152
|
+
offsetHeader: document.querySelector('.ant-layout-header')?.clientHeight || 56,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
const postData = (data: any[]) => {
|
|
159
|
+
if (!isArray(data)) {
|
|
160
|
+
return data
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
props.columns.map(column => {
|
|
164
|
+
switch (column.valueType) {
|
|
165
|
+
case 'dateTime':
|
|
166
|
+
data = data.map(row => {
|
|
167
|
+
const v = row[column.dataIndex]
|
|
168
|
+
if (parseInt(v) == v && v < 4102444800) {
|
|
169
|
+
row[column.dataIndex] *= 1000
|
|
170
|
+
}
|
|
171
|
+
return row
|
|
172
|
+
})
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return data.map(row => {
|
|
178
|
+
if (typeof row[props.rowKey] === 'undefined') {
|
|
179
|
+
row[props.rowKey] = uniqueId('row_')
|
|
180
|
+
}
|
|
181
|
+
return row
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
return <>
|
|
187
|
+
<TableContext.Provider value={{
|
|
188
|
+
getTableProps: () => props,
|
|
189
|
+
getEditedValues: () => editableValues,
|
|
190
|
+
editableKeys: editableKeys,
|
|
191
|
+
actionRef: actionRef.current,
|
|
192
|
+
formRef: formRef.current,
|
|
193
|
+
}}>
|
|
194
|
+
{!initialized && <ProSkeleton type={"list"} list={2}></ProSkeleton>}
|
|
195
|
+
<ProTable rowKey={props.rowKey}
|
|
196
|
+
style={{display: initialized ? 'block' : 'none'}}
|
|
197
|
+
tableClassName={'qs-antd-table'}
|
|
198
|
+
columns={columns}
|
|
199
|
+
onDataSourceChange={setDataSource}
|
|
200
|
+
dataSource={dataSource}
|
|
201
|
+
pagination={pagination}
|
|
202
|
+
loading={loading}
|
|
203
|
+
scroll={{x: true}}
|
|
204
|
+
postData={postData}
|
|
205
|
+
sticky={sticky}
|
|
206
|
+
form={{
|
|
207
|
+
onValuesChange(changedValues) {
|
|
208
|
+
const key = Object.keys(changedValues)[0]
|
|
209
|
+
const c = columns.find(c => c.dataIndex === key) as ProColumnType & {
|
|
210
|
+
searchOnChange: boolean
|
|
211
|
+
}
|
|
212
|
+
if (!c) {
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
// 是否立即搜索
|
|
216
|
+
if (c.searchOnChange) {
|
|
217
|
+
formRef.current?.submit()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}}
|
|
221
|
+
rowSelection={props.rowSelection && {
|
|
222
|
+
alwaysShowAlert: false,
|
|
223
|
+
selectedRowKeys: selectedRows.map(item => item[props.rowKey]),
|
|
224
|
+
onSelect(record, selected) {
|
|
225
|
+
if (selected) {
|
|
226
|
+
setSelectedRows([...selectedRows, record])
|
|
227
|
+
} else {
|
|
228
|
+
setSelectedRows(selectedRows.filter(item => item[props.rowKey] !== record[props.rowKey]))
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
onChange(selectedRowKeys, newSelectedRows, info) {
|
|
232
|
+
switch (info.type) {
|
|
233
|
+
case 'all':
|
|
234
|
+
if (newSelectedRows.length) {
|
|
235
|
+
setSelectedRows([
|
|
236
|
+
...selectedRows,
|
|
237
|
+
...newSelectedRows.filter(item => !selectedRows.find(s => s[props.rowKey] == item[props.rowKey]))
|
|
238
|
+
])
|
|
239
|
+
} else {
|
|
240
|
+
setSelectedRows(selectedRows.filter(item => !props.dataSource.find(dr => dr[props.rowKey] == item[props.rowKey])))
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
case 'none':
|
|
244
|
+
setSelectedRows([])
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
}}
|
|
249
|
+
toolbar={{
|
|
250
|
+
filter: true,
|
|
251
|
+
}}
|
|
252
|
+
toolBarRender={(action) => [
|
|
253
|
+
<ToolbarActions key={'actions'} actions={props.actions}
|
|
254
|
+
selectedRows={selectedRows}></ToolbarActions>
|
|
255
|
+
]}
|
|
256
|
+
editable={{
|
|
257
|
+
type: 'multiple',
|
|
258
|
+
editableKeys: editableKeys,
|
|
259
|
+
onChange: setEditableKeys,
|
|
260
|
+
onValuesChange(record) {
|
|
261
|
+
setEditableValues([
|
|
262
|
+
...editableValues.filter(item => item[props.rowKey] !== record[props.rowKey]),
|
|
263
|
+
record
|
|
264
|
+
])
|
|
265
|
+
}
|
|
266
|
+
}}
|
|
267
|
+
cardBordered
|
|
268
|
+
manualRequest={true}
|
|
269
|
+
request={request}
|
|
270
|
+
formRef={formRef}
|
|
271
|
+
actionRef={actionRef}
|
|
272
|
+
search={props.search}
|
|
273
|
+
dateFormatter={props.dateFormatter}
|
|
274
|
+
></ProTable>
|
|
275
|
+
|
|
276
|
+
</TableContext.Provider>
|
|
277
|
+
</>
|
|
278
|
+
}
|
|
279
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React, {createContext} from "react";
|
|
2
|
+
import {ActionType} from "@ant-design/pro-components";
|
|
3
|
+
import {TableProps} from "./Table";
|
|
4
|
+
import {FormInstance} from "antd/lib/form";
|
|
5
|
+
|
|
6
|
+
type TableContextValue = {
|
|
7
|
+
getTableProps: () => TableProps,
|
|
8
|
+
getEditedValues: () => Record<string, any>[],
|
|
9
|
+
editableKeys: React.Key[],
|
|
10
|
+
actionRef?: ActionType,
|
|
11
|
+
formRef?: FormInstance,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const TableContext = createContext({} as TableContextValue)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {Tabs} from "antd";
|
|
2
|
+
import {lazy, Suspense, useEffect, useState} from "react";
|
|
3
|
+
import type {Tab} from 'rc-tabs/lib/interface';
|
|
4
|
+
import container from "../lib/container";
|
|
5
|
+
import {routerNavigateTo} from "../lib/helpers";
|
|
6
|
+
import upperFirst from "lodash/upperFirst";
|
|
7
|
+
import {ProSkeleton} from "@ant-design/pro-components";
|
|
8
|
+
|
|
9
|
+
type TabProps = {
|
|
10
|
+
title: string,
|
|
11
|
+
url?: string,
|
|
12
|
+
pane?: {
|
|
13
|
+
component: 'form' | 'table',
|
|
14
|
+
props: any,
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type TabsPageType = {
|
|
19
|
+
tabs: Record<string, TabProps>,
|
|
20
|
+
defaultActiveKey?: string,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function (props: TabsPageType) {
|
|
24
|
+
const [items, setItems] = useState<Tab[]>([]);
|
|
25
|
+
const [activeKey, setActiveKey] = useState<string>();
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setActiveKey(props.defaultActiveKey || Object.keys(props.tabs)[0])
|
|
29
|
+
|
|
30
|
+
setItems(Object.keys(props.tabs).map(key => {
|
|
31
|
+
const t = props.tabs[key]
|
|
32
|
+
|
|
33
|
+
if (!t.pane) {
|
|
34
|
+
return {
|
|
35
|
+
key,
|
|
36
|
+
label: t.title,
|
|
37
|
+
children: <>
|
|
38
|
+
<ProSkeleton list={2}></ProSkeleton>
|
|
39
|
+
</>
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const Component = lazy(() => container.get('Tab.Pane.' + upperFirst(t.pane?.component)))
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
key,
|
|
47
|
+
label: t.title,
|
|
48
|
+
children: <>
|
|
49
|
+
<Suspense>
|
|
50
|
+
<Component {...t.pane.props}></Component>
|
|
51
|
+
</Suspense>
|
|
52
|
+
</>
|
|
53
|
+
}
|
|
54
|
+
}))
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
const onChange = (key: string) => {
|
|
58
|
+
setActiveKey(key)
|
|
59
|
+
|
|
60
|
+
const tab = props.tabs[key]
|
|
61
|
+
if (tab.url) {
|
|
62
|
+
routerNavigateTo(tab.url)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return <>
|
|
67
|
+
<Tabs items={items}
|
|
68
|
+
onChange={onChange}
|
|
69
|
+
activeKey={activeKey}
|
|
70
|
+
></Tabs>
|
|
71
|
+
</>
|
|
72
|
+
}
|
|
File without changes
|
package/lib/container.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {schemaHandler} from "./schemaHandler";
|
|
2
|
+
|
|
3
|
+
const components: Record<string, any> = {}
|
|
4
|
+
|
|
5
|
+
const container = {
|
|
6
|
+
register(name: string, componentLoader: any) {
|
|
7
|
+
if (this.check(name)) {
|
|
8
|
+
throw new Error(`Component ${name} already registered`)
|
|
9
|
+
}
|
|
10
|
+
components[name] = componentLoader
|
|
11
|
+
},
|
|
12
|
+
get(name: string) {
|
|
13
|
+
if (!this.check(name)) {
|
|
14
|
+
throw new Error(`Component ${name} is not registered`)
|
|
15
|
+
}
|
|
16
|
+
return components[name]
|
|
17
|
+
},
|
|
18
|
+
check(name: string) {
|
|
19
|
+
return !!components[name]
|
|
20
|
+
},
|
|
21
|
+
schemaHandler,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function autoRegister(prefix: string, components: Record<string, () => Promise<any>>) {
|
|
25
|
+
for (const [key, value] of Object.entries(components)) {
|
|
26
|
+
const name = key.split('/').pop()?.split('.').shift()
|
|
27
|
+
container.register(prefix + name, value)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// -------- 通用 -----------
|
|
32
|
+
{
|
|
33
|
+
const columnRender = import.meta.glob('../components/Column/*.tsx')
|
|
34
|
+
autoRegister('Column.', columnRender)
|
|
35
|
+
|
|
36
|
+
// readonly render
|
|
37
|
+
const columnReadonlyRender = import.meta.glob('../components/Column/Readonly/*.tsx')
|
|
38
|
+
autoRegister('Column.Readonly.', columnReadonlyRender)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// -------- 弹窗 -----------
|
|
42
|
+
{
|
|
43
|
+
container.register('Modal.Table', import('../components/Table'))
|
|
44
|
+
container.register('Modal.Form', import('../components/Form'))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// -------- Tabs -----------
|
|
48
|
+
{
|
|
49
|
+
container.register('Tab.Pane.Table', import('../components/Table'))
|
|
50
|
+
container.register('Tab.Pane.Form', import('../components/Form'))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// -------- 表格 -----------
|
|
54
|
+
{
|
|
55
|
+
// options render
|
|
56
|
+
const optionsRender = import.meta.glob('../components/Table/Option/*.tsx')
|
|
57
|
+
autoRegister('Table.Option.', optionsRender)
|
|
58
|
+
|
|
59
|
+
// action render
|
|
60
|
+
const actionRender = import.meta.glob('../components/Table/Action/*.tsx')
|
|
61
|
+
autoRegister('Table.Action.', actionRender)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -------- 表单 -----------
|
|
65
|
+
{
|
|
66
|
+
|
|
67
|
+
// formAction render
|
|
68
|
+
const formActionRender = import.meta.glob('../components/Form/Action/*.tsx')
|
|
69
|
+
autoRegister('Form.Action.', formActionRender)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
;((globalThis: any) => {
|
|
74
|
+
if (globalThis.$qsContainer) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
globalThis.$qsContainer = container
|
|
79
|
+
|
|
80
|
+
})(window)
|
|
81
|
+
|
|
82
|
+
export default container
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {RuleObject, StoreValue} from "rc-field-form/lib/interface";
|
|
2
|
+
|
|
3
|
+
const customRule: Record<string, (rule: RuleObject, value: StoreValue, callback: (error?: string) => void) => any> = {
|
|
4
|
+
notInEnum(rule, value) {
|
|
5
|
+
return !rule.enum?.includes(value);
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default customRule
|
package/lib/global.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {MessageInstance} from "antd/es/message/interface";
|
|
2
|
+
import {ModalStaticFunctions} from "antd/es/modal/confirm";
|
|
3
|
+
import {NotificationInstance} from "antd/es/notification/interface";
|
|
4
|
+
|
|
5
|
+
const global = {} as {
|
|
6
|
+
message: MessageInstance,
|
|
7
|
+
modal: Omit<ModalStaticFunctions, 'warn'>,
|
|
8
|
+
notification: NotificationInstance,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default global
|
package/lib/helpers.tsx
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {router} from "@inertiajs/react";
|
|
2
|
+
import {VisitOptions} from "@inertiajs/core/types/types";
|
|
3
|
+
import Schema from '@rc-component/async-validator';
|
|
4
|
+
import {Rules, ValidateError, ValidateFieldsError, Values} from "@rc-component/async-validator/lib/interface";
|
|
5
|
+
import {Spin} from "antd";
|
|
6
|
+
import http from "./http";
|
|
7
|
+
import container from "./container";
|
|
8
|
+
import upperFirst from "lodash/upperFirst";
|
|
9
|
+
import {lazy, Suspense} from "react";
|
|
10
|
+
import global from "./global";
|
|
11
|
+
import {ModalContext} from "../components/ModalContext";
|
|
12
|
+
|
|
13
|
+
export function replaceUrl(url: string, params: any) {
|
|
14
|
+
return url.replace(/__([\w]+)__/g, (match, key) => {
|
|
15
|
+
return params[key] || match;
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function replaceParams(params: Record<string, any>, data: Record<string, any>) {
|
|
20
|
+
if (typeof params !== 'object') {
|
|
21
|
+
return params;
|
|
22
|
+
}
|
|
23
|
+
const res = Object.assign({}, params);
|
|
24
|
+
Object.keys(params).forEach(key => {
|
|
25
|
+
if (typeof params[key] === 'string') {
|
|
26
|
+
const m = params[key].match(/^__(\w+)__$/)
|
|
27
|
+
if (m) {
|
|
28
|
+
res[key] = data[m[1]];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function routerNavigateTo(url: string, config?: VisitOptions) {
|
|
36
|
+
return router.visit(url, {
|
|
37
|
+
...config,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function handleRules(dataRules: Rules, data: any) {
|
|
42
|
+
return new Promise(resolve => {
|
|
43
|
+
const validator = new Schema(dataRules);
|
|
44
|
+
validator.validate(data, (errors: ValidateError[] | null, fields: ValidateFieldsError | Values) => {
|
|
45
|
+
if (errors) {
|
|
46
|
+
resolve(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
resolve(true);
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function asyncFilter(arr: any[], predicate: (item: any) => PromiseLike<any>) {
|
|
57
|
+
return await Promise.all(arr.map(predicate))
|
|
58
|
+
.then((results) => arr.filter((_v, index) => results[index]))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function modalShow(type: string, props: any) {
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function filterObjectKeys(obj: Record<string, any>, keysToKeep: string[]) {
|
|
66
|
+
if (typeof obj !== 'object' || !obj) {
|
|
67
|
+
return obj;
|
|
68
|
+
}
|
|
69
|
+
return Object.keys(obj)
|
|
70
|
+
.filter(key => !keysToKeep.includes(key))
|
|
71
|
+
.reduce((newObj, key) => {
|
|
72
|
+
newObj[key] = obj[key];
|
|
73
|
+
return newObj;
|
|
74
|
+
}, {} as Record<string, any>);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export function createScript(url: string) {
|
|
79
|
+
let scriptTags = window.document.querySelectorAll('script')
|
|
80
|
+
let len = scriptTags.length
|
|
81
|
+
let i = 0
|
|
82
|
+
let _url = window.location.origin + url
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
for (i = 0; i < len; i++) {
|
|
85
|
+
var src = scriptTags[i].src
|
|
86
|
+
if (src && src === _url) {
|
|
87
|
+
scriptTags[i].parentElement?.removeChild(scriptTags[i])
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let node = document.createElement('script')
|
|
92
|
+
node.src = url
|
|
93
|
+
node.onload = resolve
|
|
94
|
+
document.body.appendChild(node)
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function modal(options: ModalOptions) {
|
|
99
|
+
let props = options.content.props
|
|
100
|
+
if (options.content.url) {
|
|
101
|
+
const res = await http({
|
|
102
|
+
method: 'get',
|
|
103
|
+
url: options.content.url,
|
|
104
|
+
headers: {
|
|
105
|
+
'X-Modal': '1',
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
if (typeof res.data === 'string') {
|
|
109
|
+
throw new Error('modal response is not vail')
|
|
110
|
+
}
|
|
111
|
+
props = res.data
|
|
112
|
+
}
|
|
113
|
+
if (!props) {
|
|
114
|
+
throw new Error('modal props is empty')
|
|
115
|
+
}
|
|
116
|
+
const Component = lazy(() => container.get('Modal.' + upperFirst(options.content.type)))
|
|
117
|
+
|
|
118
|
+
let afterClose = () => {
|
|
119
|
+
}
|
|
120
|
+
const modal = global.modal.info({
|
|
121
|
+
...options,
|
|
122
|
+
closable: true,
|
|
123
|
+
icon: null,
|
|
124
|
+
destroyOnClose: true,
|
|
125
|
+
footer: null,
|
|
126
|
+
content: (
|
|
127
|
+
<Suspense fallback={<Spin/>}>
|
|
128
|
+
<ModalContext.Provider value={{
|
|
129
|
+
inModal: true,
|
|
130
|
+
closeModal: () => {
|
|
131
|
+
modal?.destroy()
|
|
132
|
+
},
|
|
133
|
+
contexts: options.contexts,
|
|
134
|
+
setAfterClose(callback: () => void) {
|
|
135
|
+
afterClose = callback
|
|
136
|
+
}
|
|
137
|
+
}}>
|
|
138
|
+
<Component {...props} />
|
|
139
|
+
</ModalContext.Provider>
|
|
140
|
+
</Suspense>
|
|
141
|
+
),
|
|
142
|
+
afterClose: () => {
|
|
143
|
+
afterClose && afterClose()
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
return {
|
|
147
|
+
destroy: modal.destroy,
|
|
148
|
+
update: modal.update,
|
|
149
|
+
}
|
|
150
|
+
}
|
package/lib/http.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import axios, {AxiosError} from "axios";
|
|
2
|
+
import {routerNavigateTo} from "./helpers";
|
|
3
|
+
import global from "./global";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* fetchOptions.noHandle 成功时不处理 url 和 info
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const http = axios.create({})
|
|
10
|
+
|
|
11
|
+
http.interceptors.request.use(config => {
|
|
12
|
+
config.headers['Accept'] = 'application/json'
|
|
13
|
+
// 设置异步模式
|
|
14
|
+
config.headers['X-Requested-With'] = 'XMLHttpRequest'
|
|
15
|
+
return config
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
http.interceptors.response.use(response => {
|
|
19
|
+
const checkInfo = (data: { status?: number, info?: string }) => {
|
|
20
|
+
if (!data?.info) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
switch (data.status) {
|
|
24
|
+
case 0:
|
|
25
|
+
global.notification.warning({
|
|
26
|
+
message: data.info
|
|
27
|
+
})
|
|
28
|
+
break
|
|
29
|
+
default:
|
|
30
|
+
global.notification.success({
|
|
31
|
+
message: data.info
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (response.config.fetchOptions?.noHandle) {
|
|
38
|
+
return response
|
|
39
|
+
}
|
|
40
|
+
const showInfo = checkInfo(response.data)
|
|
41
|
+
|
|
42
|
+
if (response.data.url) {
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
routerNavigateTo(response.data.url)
|
|
45
|
+
}, showInfo ? 2000 : 0)
|
|
46
|
+
}
|
|
47
|
+
if (response.data.status == 0) {
|
|
48
|
+
return Promise.reject(response.data.info)
|
|
49
|
+
}
|
|
50
|
+
return response
|
|
51
|
+
}, error => {
|
|
52
|
+
if (error instanceof AxiosError) {
|
|
53
|
+
if (error.response?.headers['content-type'].includes('application/json')) {
|
|
54
|
+
global.notification.error({
|
|
55
|
+
message: error.response?.data?.info || '请求错误,请稍候重试'
|
|
56
|
+
})
|
|
57
|
+
} else if (error.response?.headers['content-type'].includes('text/html')) {
|
|
58
|
+
const parser = new DOMParser;
|
|
59
|
+
const doc = parser.parseFromString(error.response?.data, 'text/html');
|
|
60
|
+
const title = doc.querySelector('title')?.textContent;
|
|
61
|
+
|
|
62
|
+
global.notification.error({
|
|
63
|
+
message: title || '请求错误,请稍候重试'
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
global.notification.error({
|
|
68
|
+
message: '请求错误,请稍候重试'
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
return Promise.reject(error)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
export default http
|