index.tsx
4.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// REQ-USR-001 / REQ-USR-002: 用户单据页面容器(判 mode、装配 4 子组件、提交反馈与导航回流)
import { useEffect } from 'react';
import { Form, Spin, Button, Modal, App as AntdApp } from 'antd';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import type { UserVO } from '../../../api/types';
import UserDetailToolbar from './UserDetailToolbar';
import UserBasicForm from './UserBasicForm';
import PermissionTabs from './PermissionTabs';
import PermissionGroupList from './PermissionGroupList';
import { useUserDetail } from './useUserDetail';
import {
MODE_CREATE,
MODE_EDIT,
MSG_CREATE_SUCCESS,
MSG_EDIT_SUCCESS,
MSG_ERR_USER_NOT_FOUND,
MSG_LOAD_DETAIL_FAIL,
MSG_CANCEL_CONFIRM,
PATH_USER_LIST,
PATH_USER_NEW,
TEXT_BACK_TO_LIST,
TEXT_RETRY,
type UserFormValues,
} from './constants';
import styles from './UserDetail.module.css';
/** Checkbox 受控值为 boolean,提交映射需 0/1(BR8) */
function normalizeFormValues(raw: UserFormValues): UserFormValues {
return {
...raw,
iCanModifyBill: (raw.iCanModifyBill ? 1 : 0) as 0 | 1,
};
}
export default function UserDetailPage() {
const navigate = useNavigate();
const params = useParams<{ id?: string }>();
const location = useLocation();
const { message } = AntdApp.useApp();
const [form] = Form.useForm<UserFormValues>();
const mode = params.id ? MODE_EDIT : MODE_CREATE;
const userId = params.id ? Number(params.id) : undefined;
const presetUser = (location.state as { user?: UserVO } | null)?.user ?? null;
const detail = useUserDetail({ mode, userId, presetUser });
const {
formValues,
employees,
permissions,
checkedPermissionIds,
readonlyCreator,
readonlyCreateTime,
loading,
submitting,
loadFailed,
notFound,
selectEmployee,
togglePermission,
toggleAll,
submit,
reload,
} = detail;
// hook 持有的受控值回写到 AntD Form(create 默认 / edit 回填,BR1/BR2/BR6/BR17)
useEffect(() => {
form.setFieldsValue({
...formValues,
iCanModifyBill: (formValues.iCanModifyBill ? 1 : 0) as 0 | 1,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formValues]);
const handleSave = async () => {
try {
const values = await form.validateFields();
const ret = await submit(normalizeFormValues({ ...formValues, ...values }));
if (ret.ok) {
message.success(mode === MODE_CREATE ? MSG_CREATE_SUCCESS : MSG_EDIT_SUCCESS);
navigate(PATH_USER_LIST);
} else if (ret.fieldError) {
form.setFields([
{ name: ret.fieldError.field, errors: [ret.fieldError.message] },
]);
}
} catch {
// validateFields 失败:就近字段已展示错误,不发请求(BR12)
}
};
const handleCancel = () => {
if (form.isFieldsTouched()) {
Modal.confirm({
title: MSG_CANCEL_CONFIRM,
onOk: () => navigate(PATH_USER_LIST),
});
} else {
navigate(PATH_USER_LIST);
}
};
const handleNew = () => {
navigate(PATH_USER_NEW);
};
const handleSelectEmployee = (value: number | null) => {
selectEmployee(value);
};
// edit 详情不存在(40401):返回列表入口(spec § 4)
if (notFound) {
return (
<div className={styles.page}>
<div className={styles.loadError} data-testid="userdetail-notfound">
<span className={styles.loadErrorText}>{MSG_ERR_USER_NOT_FOUND}</span>
<Button type="primary" onClick={() => navigate(PATH_USER_LIST)}>
{TEXT_BACK_TO_LIST}
</Button>
</div>
</div>
);
}
// 预取/详情取数失败:重试入口(spec § 4 loadError)
if (loadFailed) {
return (
<div className={styles.page}>
<div className={styles.loadError} data-testid="userdetail-loaderror">
<span className={styles.loadErrorText}>{MSG_LOAD_DETAIL_FAIL}</span>
<Button type="primary" onClick={() => reload()}>
{TEXT_RETRY}
</Button>
</div>
</div>
);
}
return (
<div className={styles.page} data-testid="userdetail-page">
<UserDetailToolbar
mode={mode}
submitting={submitting}
canSave={!loading}
onSave={() => void handleSave()}
onCancel={handleCancel}
onNew={handleNew}
/>
<Spin spinning={loading}>
<Form form={form} layout="vertical" component={false}>
<UserBasicForm
form={form}
mode={mode}
employees={employees}
readonlyCreator={readonlyCreator}
readonlyCreateTime={readonlyCreateTime}
onSelectEmployee={handleSelectEmployee}
/>
</Form>
<PermissionTabs>
<PermissionGroupList
permissions={permissions}
checkedIds={checkedPermissionIds}
onToggle={togglePermission}
onToggleAll={toggleAll}
/>
</PermissionTabs>
</Spin>
</div>
);
}