fix: npm lint errors
This commit is contained in:
@@ -6,16 +6,16 @@ import { API_CONFIG } from './config';
|
|||||||
export interface RequestOptions {
|
export interface RequestOptions {
|
||||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
body?: any;
|
body?: unknown;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义错误类
|
// 自定义错误类
|
||||||
export class ApiError extends Error {
|
export class ApiError extends Error {
|
||||||
status?: number;
|
status?: number;
|
||||||
data?: any;
|
data?: unknown;
|
||||||
|
|
||||||
constructor(message: string, status?: number, data?: any) {
|
constructor(message: string, status?: number, data?: unknown) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'ApiError';
|
this.name = 'ApiError';
|
||||||
this.status = status;
|
this.status = status;
|
||||||
@@ -24,7 +24,7 @@ export class ApiError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 基础请求函数
|
// 基础请求函数
|
||||||
export async function request<T = any>(
|
export async function request<T = unknown>(
|
||||||
url: string,
|
url: string,
|
||||||
options: RequestOptions = {}
|
options: RequestOptions = {}
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
@@ -67,18 +67,19 @@ export async function request<T = any>(
|
|||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
let errorData: any;
|
let errorData: unknown;
|
||||||
try {
|
try {
|
||||||
errorData = await response.json();
|
errorData = await response.json();
|
||||||
} catch {
|
} catch {
|
||||||
errorData = { message: response.statusText };
|
errorData = { message: response.statusText };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ApiError(
|
let messageText = `HTTP ${response.status}: ${response.statusText}`;
|
||||||
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
|
if (errorData && typeof errorData === 'object') {
|
||||||
response.status,
|
const maybe = errorData as { message?: string };
|
||||||
errorData
|
messageText = maybe.message ?? messageText;
|
||||||
);
|
}
|
||||||
|
throw new ApiError(messageText, response.status, errorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查响应是否有内容
|
// 检查响应是否有内容
|
||||||
@@ -108,7 +109,8 @@ export async function request<T = any>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GET 请求
|
// GET 请求
|
||||||
export function get<T = any>(url: string, params?: Record<string, any>): Promise<T> {
|
type QueryParamValue = string | number | boolean | null | undefined;
|
||||||
|
export function get<T = unknown>(url: string, params?: Record<string, QueryParamValue>): Promise<T> {
|
||||||
let fullUrl = url;
|
let fullUrl = url;
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
@@ -129,7 +131,7 @@ export function get<T = any>(url: string, params?: Record<string, any>): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
// POST 请求
|
// POST 请求
|
||||||
export function post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
|
export function post<T = unknown>(url: string, data?: unknown, options?: Partial<RequestOptions>): Promise<T> {
|
||||||
return request<T>(url, {
|
return request<T>(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: data,
|
body: data,
|
||||||
@@ -138,7 +140,7 @@ export function post<T = any>(url: string, data?: any, options?: Partial<Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PUT 请求
|
// PUT 请求
|
||||||
export function put<T = any>(url: string, data?: any): Promise<T> {
|
export function put<T = unknown>(url: string, data?: unknown): Promise<T> {
|
||||||
return request<T>(url, {
|
return request<T>(url, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: data,
|
body: data,
|
||||||
@@ -146,12 +148,12 @@ export function put<T = any>(url: string, data?: any): Promise<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DELETE 请求
|
// DELETE 请求
|
||||||
export function del<T = any>(url: string): Promise<T> {
|
export function del<T = unknown>(url: string): Promise<T> {
|
||||||
return request<T>(url, { method: 'DELETE' });
|
return request<T>(url, { method: 'DELETE' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文件上传请求
|
// 文件上传请求
|
||||||
export function upload<T = any>(url: string, file: File, fieldName = 'file', options?: Partial<RequestOptions>): Promise<T> {
|
export function upload<T = unknown>(url: string, file: File, fieldName = 'file', options?: Partial<RequestOptions>): Promise<T> {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(fieldName, file);
|
formData.append(fieldName, file);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// API 请求和响应类型定义
|
// API 请求和响应类型定义
|
||||||
|
|
||||||
// 基础响应类型
|
// 基础响应类型
|
||||||
export interface ApiResponse<T = any> {
|
export interface ApiResponse<T = unknown> {
|
||||||
data?: T;
|
data?: T;
|
||||||
error_code: number;
|
error_code: number;
|
||||||
error_info?: string;
|
error_info?: string;
|
||||||
@@ -25,7 +25,7 @@ export interface PostInputRequest {
|
|||||||
|
|
||||||
// 人员信息请求类型
|
// 人员信息请求类型
|
||||||
export interface PostPeopleRequest {
|
export interface PostPeopleRequest {
|
||||||
people: Record<string, any>;
|
people: People;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 人员查询参数类型
|
// 人员查询参数类型
|
||||||
@@ -39,6 +39,7 @@ export interface GetPeoplesParams {
|
|||||||
offset?: number;
|
offset?: number;
|
||||||
search?: string;
|
search?: string;
|
||||||
top_k?: number;
|
top_k?: number;
|
||||||
|
[key: string]: string | number | boolean | null | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 人员信息类型
|
// 人员信息类型
|
||||||
@@ -52,8 +53,9 @@ export interface People {
|
|||||||
marital_status?: string;
|
marital_status?: string;
|
||||||
created_at?: number;
|
created_at?: number;
|
||||||
match_requirement?: string;
|
match_requirement?: string;
|
||||||
[key: string]: any;
|
|
||||||
cover?: string;
|
cover?: string;
|
||||||
|
introduction?: Record<string, string>;
|
||||||
|
comments?: { remark?: { content: string; updated_at: number } };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页响应类型
|
// 分页响应类型
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ export async function postInputImageWithProgress(
|
|||||||
try {
|
try {
|
||||||
const response = xhr.responseText ? JSON.parse(xhr.responseText) : {};
|
const response = xhr.responseText ? JSON.parse(xhr.responseText) : {};
|
||||||
resolve(response);
|
resolve(response);
|
||||||
} catch (error) {
|
} catch {
|
||||||
resolve({error_code: 1, error_info: '解析响应失败'});
|
resolve({ error_code: 1, error_info: '解析响应失败' });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
|
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import PeopleForm from './PeopleForm.tsx'
|
|||||||
import InputDrawer from './InputDrawer.tsx'
|
import InputDrawer from './InputDrawer.tsx'
|
||||||
import { createPeoplesBatch, type People } from '../apis'
|
import { createPeoplesBatch, type People } from '../apis'
|
||||||
|
|
||||||
const { Panel } = Collapse as any
|
const Panel = Collapse.Panel
|
||||||
const { Content } = Layout
|
const { Content } = Layout
|
||||||
|
|
||||||
type FormItem = { id: string; initialData?: any }
|
type FormItem = { id: string; initialData?: Partial<People> }
|
||||||
|
|
||||||
type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null }
|
type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null }
|
||||||
|
|
||||||
@@ -29,7 +29,18 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
|||||||
delete instancesRef.current[id]
|
delete instancesRef.current[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildPeople = (values: any, common: any): People => {
|
type FormValues = {
|
||||||
|
name: string;
|
||||||
|
contact?: string;
|
||||||
|
gender: string;
|
||||||
|
age: number;
|
||||||
|
height?: number;
|
||||||
|
marital_status?: string;
|
||||||
|
introduction?: Record<string, string>;
|
||||||
|
match_requirement?: string;
|
||||||
|
cover?: string;
|
||||||
|
}
|
||||||
|
const buildPeople = (values: FormValues, common: { contact?: string }): People => {
|
||||||
return {
|
return {
|
||||||
name: values.name,
|
name: values.name,
|
||||||
contact: values.contact || common.contact || undefined,
|
contact: values.contact || common.contact || undefined,
|
||||||
@@ -43,9 +54,9 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInputResult = (list: any) => {
|
const handleInputResult = (list: unknown) => {
|
||||||
const arr = Array.isArray(list) ? list : [list]
|
const arr = Array.isArray(list) ? list : [list]
|
||||||
const next: FormItem[] = arr.map((data: any) => ({ id: `${Date.now()}-${Math.random()}`, initialData: data }))
|
const next: FormItem[] = arr.map((data) => ({ id: `${Date.now()}-${Math.random()}`, initialData: data as Partial<People> }))
|
||||||
setItems((prev) => [...prev, ...next])
|
setItems((prev) => [...prev, ...next])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +72,12 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
|||||||
message.error('表单未就绪')
|
message.error('表单未就绪')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const allValues: any[] = []
|
const allValues: FormValues[] = []
|
||||||
for (const f of forms) {
|
for (const f of forms) {
|
||||||
try {
|
try {
|
||||||
const v = await f.validateFields()
|
const v = await f.validateFields()
|
||||||
allValues.push(v)
|
allValues.push(v)
|
||||||
} catch (err: any) {
|
} catch {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
message.error('请完善全部表单后再提交')
|
message.error('请完善全部表单后再提交')
|
||||||
return
|
return
|
||||||
@@ -87,7 +98,7 @@ const BatchRegister: React.FC<Props> = ({ inputOpen = false, onCloseInput, conta
|
|||||||
setItems([{ id: `${Date.now()}-${Math.random()}` }])
|
setItems([{ id: `${Date.now()}-${Math.random()}` }])
|
||||||
commonForm.resetFields()
|
commonForm.resetFields()
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch {
|
||||||
message.error('提交失败')
|
message.error('提交失败')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import './InputDrawer.css';
|
|||||||
type Props = {
|
type Props = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onResult?: (data: any) => void;
|
onResult?: (data: unknown) => void;
|
||||||
containerEl?: HTMLElement | null; // 抽屉挂载容器(用于放在标题栏下方)
|
containerEl?: HTMLElement | null; // 抽屉挂载容器(用于放在标题栏下方)
|
||||||
showUpload?: boolean; // 透传到输入面板,控制图片上传按钮
|
showUpload?: boolean; // 透传到输入面板,控制图片上传按钮
|
||||||
mode?: 'input' | 'search' | 'batch-image'; // 透传到输入面板,控制工作模式
|
mode?: 'input' | 'search' | 'batch-image'; // 透传到输入面板,控制工作模式
|
||||||
@@ -31,7 +31,7 @@ const InputDrawer: React.FC<Props> = ({ open, onClose, onResult, containerEl, sh
|
|||||||
|
|
||||||
// 在输入处理成功(onResult 被调用)后,自动关闭抽屉
|
// 在输入处理成功(onResult 被调用)后,自动关闭抽屉
|
||||||
const handleResult = React.useCallback(
|
const handleResult = React.useCallback(
|
||||||
(data: any) => {
|
(data: unknown) => {
|
||||||
onResult?.(data);
|
onResult?.(data);
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Input, Upload, message, Button, Spin, Tag } from 'antd';
|
import { Input, Upload, message, Button, Spin, Tag } from 'antd';
|
||||||
|
import type { UploadFile, RcFile } from 'antd/es/upload/interface';
|
||||||
import { PictureOutlined, SendOutlined, LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
import { PictureOutlined, SendOutlined, LoadingOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { postInput, postInputImage, getPeoples } from '../apis';
|
import { postInput, postInputImage, getPeoples } from '../apis';
|
||||||
import './InputPanel.css';
|
import './InputPanel.css';
|
||||||
@@ -7,14 +8,14 @@ import './InputPanel.css';
|
|||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
interface InputPanelProps {
|
interface InputPanelProps {
|
||||||
onResult?: (data: any) => void;
|
onResult?: (data: unknown) => void;
|
||||||
showUpload?: boolean; // 是否显示图片上传按钮,默认显示
|
showUpload?: boolean; // 是否显示图片上传按钮,默认显示
|
||||||
mode?: 'input' | 'search' | 'batch-image'; // 输入面板工作模式,新增批量图片模式
|
mode?: 'input' | 'search' | 'batch-image'; // 输入面板工作模式,新增批量图片模式
|
||||||
}
|
}
|
||||||
|
|
||||||
const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mode = 'input' }) => {
|
const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mode = 'input' }) => {
|
||||||
const [value, setValue] = React.useState('');
|
const [value, setValue] = React.useState('');
|
||||||
const [fileList, setFileList] = React.useState<any[]>([]);
|
const [fileList, setFileList] = React.useState<UploadFile[]>([]);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
// 批量模式不保留文本内容
|
// 批量模式不保留文本内容
|
||||||
|
|
||||||
@@ -63,9 +64,9 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
}
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const results: any[] = [];
|
const results: unknown[] = [];
|
||||||
for (let i = 0; i < fileList.length; i++) {
|
for (let i = 0; i < fileList.length; i++) {
|
||||||
const f = fileList[i].originFileObj || fileList[i];
|
const f = fileList[i].originFileObj as RcFile | undefined;
|
||||||
if (!f) continue;
|
if (!f) continue;
|
||||||
const resp = await postInputImage(f);
|
const resp = await postInputImage(f);
|
||||||
if (resp && resp.error_code === 0 && resp.data) {
|
if (resp && resp.error_code === 0 && resp.data) {
|
||||||
@@ -95,7 +96,7 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
|
|
||||||
// 如果有图片,优先处理图片上传
|
// 如果有图片,优先处理图片上传
|
||||||
if (hasImage) {
|
if (hasImage) {
|
||||||
const file = fileList[0].originFileObj || fileList[0];
|
const file = fileList[0].originFileObj as RcFile | undefined;
|
||||||
if (!file) {
|
if (!file) {
|
||||||
message.error('图片文件无效,请重新选择');
|
message.error('图片文件无效,请重新选择');
|
||||||
return;
|
return;
|
||||||
@@ -152,18 +153,18 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
if (!items || items.length === 0) return;
|
if (!items || items.length === 0) return;
|
||||||
|
|
||||||
if (mode === 'batch-image') {
|
if (mode === 'batch-image') {
|
||||||
const newEntries: any[] = [];
|
const newEntries: UploadFile[] = [];
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
if (item.kind === 'file') {
|
if (item.kind === 'file') {
|
||||||
const file = item.getAsFile();
|
const file = item.getAsFile();
|
||||||
if (file && file.type.startsWith('image/')) {
|
if (file && file.type.startsWith('image/')) {
|
||||||
const entry = {
|
const entry: UploadFile<RcFile> = {
|
||||||
uid: `${Date.now()}-${Math.random()}`,
|
uid: `${Date.now()}-${Math.random()}`,
|
||||||
name: 'image',
|
name: 'image',
|
||||||
status: 'done',
|
status: 'done',
|
||||||
originFileObj: file,
|
originFileObj: file as unknown as RcFile,
|
||||||
} as any;
|
};
|
||||||
newEntries.push(entry);
|
newEntries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,14 +191,13 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
if (firstImage) {
|
if (firstImage) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setValue('');
|
setValue('');
|
||||||
setFileList([
|
const item: UploadFile<RcFile> = {
|
||||||
{
|
uid: `${Date.now()}-${Math.random()}`,
|
||||||
uid: `${Date.now()}-${Math.random()}`,
|
name: 'image',
|
||||||
name: 'image',
|
status: 'done',
|
||||||
status: 'done',
|
originFileObj: firstImage as unknown as RcFile,
|
||||||
originFileObj: firstImage,
|
};
|
||||||
} as any,
|
setFileList([item]);
|
||||||
]);
|
|
||||||
message.success('已添加剪贴板图片');
|
message.success('已添加剪贴板图片');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,9 +278,9 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
fileList={fileList}
|
fileList={fileList}
|
||||||
onChange={({ fileList: nextFileList }) => {
|
onChange={({ fileList: nextFileList }) => {
|
||||||
if (mode === 'batch-image') {
|
if (mode === 'batch-image') {
|
||||||
const normalized = nextFileList.map((entry: any) => {
|
const normalized: UploadFile<RcFile>[] = nextFileList.map((entry) => {
|
||||||
const raw = entry.originFileObj || entry;
|
const raw = (entry as UploadFile<RcFile>).originFileObj;
|
||||||
return { ...entry, name: 'image', originFileObj: raw };
|
return { ...(entry as UploadFile<RcFile>), name: 'image', originFileObj: raw };
|
||||||
});
|
});
|
||||||
setValue('');
|
setValue('');
|
||||||
setFileList(normalized);
|
setFileList(normalized);
|
||||||
@@ -290,15 +290,15 @@ const InputPanel: React.FC<InputPanelProps> = ({ onResult, showUpload = true, mo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 仅添加第一张
|
// 仅添加第一张
|
||||||
const first = nextFileList[0] as any;
|
const first = nextFileList[0] as UploadFile<RcFile>;
|
||||||
const raw = first.originFileObj || first;
|
const raw = first.originFileObj;
|
||||||
const renamed = { ...first, name: 'image', originFileObj: raw };
|
const renamed: UploadFile<RcFile> = { ...first, name: 'image', originFileObj: raw };
|
||||||
setValue('');
|
setValue('');
|
||||||
setFileList([renamed]);
|
setFileList([renamed]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onRemove={(file) => {
|
onRemove={(file) => {
|
||||||
setFileList((prev) => prev.filter((x) => x.uid !== (file as any).uid));
|
setFileList((prev) => prev.filter((x) => x.uid !== (file as UploadFile<RcFile>).uid));
|
||||||
return true;
|
return true;
|
||||||
}}
|
}}
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Row, Col, Input, Button } from 'antd';
|
import { Row, Col, Input, Button } from 'antd';
|
||||||
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import './KeyValueList.css';
|
import './KeyValueList.css';
|
||||||
@@ -15,11 +15,8 @@ type Props = {
|
|||||||
const KeyValueList: React.FC<Props> = ({ value, onChange }) => {
|
const KeyValueList: React.FC<Props> = ({ value, onChange }) => {
|
||||||
const [rows, setRows] = useState<KeyValuePair[]>([]);
|
const [rows, setRows] = useState<KeyValuePair[]>([]);
|
||||||
|
|
||||||
|
const initializedRef = useRef(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 初始化时提供一行空输入;之后只合并父值,不再自动新增空行
|
|
||||||
const initializedRef = (KeyValueList as any)._initializedRef || { current: false };
|
|
||||||
(KeyValueList as any)._initializedRef = initializedRef;
|
|
||||||
|
|
||||||
setRows((prev) => {
|
setRows((prev) => {
|
||||||
const existingIdByKey = new Map(prev.filter((r) => r.k).map((r) => [r.k, r.id]));
|
const existingIdByKey = new Map(prev.filter((r) => r.k).map((r) => [r.k, r.id]));
|
||||||
const valuePairs: KeyValuePair[] = value
|
const valuePairs: KeyValuePair[] = value
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const LayoutWrapper: React.FC = () => {
|
|||||||
showInput={isHome || isList || isBatch}
|
showInput={isHome || isList || isBatch}
|
||||||
/>
|
/>
|
||||||
{/* 下方为主布局:左侧菜单 + 右侧内容 */}
|
{/* 下方为主布局:左侧菜单 + 右侧内容 */}
|
||||||
<Layout ref={layoutShellRef as any} className="layout-shell">
|
<Layout ref={layoutShellRef} className="layout-shell">
|
||||||
<SiderMenu
|
<SiderMenu
|
||||||
onNavigate={handleNavigate}
|
onNavigate={handleNavigate}
|
||||||
selectedKey={selectedKey}
|
selectedKey={selectedKey}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal, Form, Input, Button, message } from 'antd';
|
import { Modal, Form, Input, Button, message } from 'antd';
|
||||||
|
import type { LoginRequest } from '../apis/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onOk: (values: any) => Promise<void>;
|
onOk: (values: LoginRequest) => Promise<void>;
|
||||||
title: string;
|
title: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
usernameReadOnly?: boolean;
|
usernameReadOnly?: boolean;
|
||||||
@@ -30,12 +31,12 @@ const LoginModal: React.FC<Props> = ({ open, onCancel, onOk, title, username, us
|
|||||||
const payload = isEmail
|
const payload = isEmail
|
||||||
? { email: username, password }
|
? { email: username, password }
|
||||||
: { phone: username, password };
|
: { phone: username, password };
|
||||||
await onOk(payload as any);
|
await onOk(payload as LoginRequest);
|
||||||
if (!hideSuccessMessage) {
|
if (!hideSuccessMessage) {
|
||||||
message.success('登录成功');
|
message.success('登录成功');
|
||||||
}
|
}
|
||||||
onCancel();
|
onCancel();
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error('登录失败');
|
message.error('登录失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ import { Layout, Typography } from 'antd';
|
|||||||
import PeopleForm from './PeopleForm.tsx';
|
import PeopleForm from './PeopleForm.tsx';
|
||||||
import InputDrawer from './InputDrawer.tsx';
|
import InputDrawer from './InputDrawer.tsx';
|
||||||
import './MainContent.css';
|
import './MainContent.css';
|
||||||
|
import type { People } from '../apis';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null };
|
type Props = { inputOpen?: boolean; onCloseInput?: () => void; containerEl?: HTMLElement | null };
|
||||||
const MainContent: React.FC<Props> = ({ inputOpen = false, onCloseInput, containerEl }) => {
|
const MainContent: React.FC<Props> = ({ inputOpen = false, onCloseInput, containerEl }) => {
|
||||||
const [formData, setFormData] = React.useState<any>(null);
|
const [formData, setFormData] = React.useState<Partial<People> | null>(null);
|
||||||
|
|
||||||
const handleInputResult = (data: any) => {
|
const handleInputResult = (data: unknown) => {
|
||||||
setFormData(data);
|
setFormData(data as Partial<People>);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -24,7 +25,7 @@ const MainContent: React.FC<Props> = ({ inputOpen = false, onCloseInput, contain
|
|||||||
点击右上角可以直接输入描述或上传图片
|
点击右上角可以直接输入描述或上传图片
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
|
|
||||||
<PeopleForm initialData={formData} />
|
<PeopleForm initialData={formData || undefined} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 首页右侧输入抽屉,仅在顶栏点击后弹出;挂载到标题栏下方容器 */}
|
{/* 首页右侧输入抽屉,仅在顶栏点击后弹出;挂载到标题栏下方容器 */}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { createPeople, type People } from '../apis';
|
|||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
interface PeopleFormProps {
|
interface PeopleFormProps {
|
||||||
initialData?: any;
|
initialData?: Partial<People>;
|
||||||
// 编辑模式下由父组件控制提交,隐藏内部提交按钮
|
// 编辑模式下由父组件控制提交,隐藏内部提交按钮
|
||||||
hideSubmitButton?: boolean;
|
hideSubmitButton?: boolean;
|
||||||
// 暴露 AntD Form 实例给父组件,用于在外部触发校验与取值
|
// 暴露 AntD Form 实例给父组件,用于在外部触发校验与取值
|
||||||
@@ -22,11 +22,7 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
|||||||
// 当 initialData 变化时,自动填充表单
|
// 当 initialData 变化时,自动填充表单
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
console.log('收到API返回数据,自动填充表单:', initialData);
|
const formData: Partial<People> = {};
|
||||||
|
|
||||||
// 处理返回的数据,将其转换为表单需要的格式
|
|
||||||
const formData: any = {};
|
|
||||||
|
|
||||||
if (initialData.name) formData.name = initialData.name;
|
if (initialData.name) formData.name = initialData.name;
|
||||||
if (initialData.contact) formData.contact = initialData.contact;
|
if (initialData.contact) formData.contact = initialData.contact;
|
||||||
if (initialData.cover) formData.cover = initialData.cover;
|
if (initialData.cover) formData.cover = initialData.cover;
|
||||||
@@ -35,13 +31,8 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
|||||||
if (initialData.height) formData.height = initialData.height;
|
if (initialData.height) formData.height = initialData.height;
|
||||||
if (initialData.marital_status) formData.marital_status = initialData.marital_status;
|
if (initialData.marital_status) formData.marital_status = initialData.marital_status;
|
||||||
if (initialData.match_requirement) formData.match_requirement = initialData.match_requirement;
|
if (initialData.match_requirement) formData.match_requirement = initialData.match_requirement;
|
||||||
if (initialData.introduction) formData.introduction = initialData.introduction;
|
if (initialData.introduction) formData.introduction = initialData.introduction as Record<string, string>;
|
||||||
|
|
||||||
// 设置表单字段值
|
|
||||||
form.setFieldsValue(formData);
|
form.setFieldsValue(formData);
|
||||||
|
|
||||||
// 显示成功消息
|
|
||||||
// message.success('已自动填充表单,请检查并确认信息');
|
|
||||||
}
|
}
|
||||||
}, [initialData, form]);
|
}, [initialData, form]);
|
||||||
|
|
||||||
@@ -52,7 +43,18 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onFinish = async (values: any) => {
|
type FormValues = {
|
||||||
|
name: string;
|
||||||
|
contact?: string;
|
||||||
|
gender: string;
|
||||||
|
age: number;
|
||||||
|
height?: number;
|
||||||
|
marital_status?: string;
|
||||||
|
introduction?: Record<string, string>;
|
||||||
|
match_requirement?: string;
|
||||||
|
cover?: string;
|
||||||
|
};
|
||||||
|
const onFinish = async (values: FormValues) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -81,16 +83,14 @@ const PeopleForm: React.FC<PeopleFormProps> = ({ initialData, hideSubmitButton =
|
|||||||
message.error(response.error_info || '提交失败,请重试');
|
message.error(response.error_info || '提交失败,请重试');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (e) {
|
||||||
console.error('提交失败:', error);
|
const err = e as { status?: number; message?: string };
|
||||||
|
if (err.status === 422) {
|
||||||
// 根据错误类型显示不同的错误信息
|
|
||||||
if (error.status === 422) {
|
|
||||||
message.error('表单数据格式有误,请检查输入内容');
|
message.error('表单数据格式有误,请检查输入内容');
|
||||||
} else if (error.status >= 500) {
|
} else if ((err.status ?? 0) >= 500) {
|
||||||
message.error('服务器错误,请稍后重试');
|
message.error('服务器错误,请稍后重试');
|
||||||
} else {
|
} else {
|
||||||
message.error(error.message || '提交失败,请重试');
|
message.error(err.message || '提交失败,请重试');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, Form, Input, Button, message } from 'antd';
|
import { Modal, Form, Input, Button, message } from 'antd';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/useAuth';
|
||||||
import type { RegisterRequest } from '../apis/types';
|
import type { RegisterRequest } from '../apis/types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -29,7 +29,7 @@ const RegisterModal: React.FC<Props> = ({ open, onCancel }) => {
|
|||||||
await sendCode({ target_type, target, scene: 'register' });
|
await sendCode({ target_type, target, scene: 'register' });
|
||||||
message.success('验证码已发送');
|
message.success('验证码已发送');
|
||||||
setStep('verify');
|
setStep('verify');
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error('发送验证码失败');
|
message.error('发送验证码失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -40,7 +40,7 @@ const RegisterModal: React.FC<Props> = ({ open, onCancel }) => {
|
|||||||
await register({ ...registerPayload, ...values } as RegisterRequest);
|
await register({ ...registerPayload, ...values } as RegisterRequest);
|
||||||
message.success('注册成功');
|
message.success('注册成功');
|
||||||
onCancel();
|
onCancel();
|
||||||
} catch (error) {
|
} catch {
|
||||||
message.error('注册失败');
|
message.error('注册失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export type Resource = Omit<People, 'id'> & { id: string };
|
|||||||
|
|
||||||
// 统一转换 API 返回的人员列表为表格需要的结构
|
// 统一转换 API 返回的人员列表为表格需要的结构
|
||||||
function transformPeoples(list: People[] = []): Resource[] {
|
function transformPeoples(list: People[] = []): Resource[] {
|
||||||
return (list || []).map((person: any) => ({
|
return (list || []).map((person: Partial<People>) => ({
|
||||||
id: person.id || `person-${Date.now()}-${Math.random()}`,
|
id: person.id || `person-${Date.now()}-${Math.random()}`,
|
||||||
name: person.name || '未知',
|
name: person.name || '未知',
|
||||||
gender: person.gender || '其他/保密',
|
gender: person.gender || '其他/保密',
|
||||||
@@ -72,7 +72,7 @@ async function fetchResources(): Promise<Resource[]> {
|
|||||||
|
|
||||||
return transformed;
|
return transformed;
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('获取人员列表失败:', error);
|
console.error('获取人员列表失败:', error);
|
||||||
message.error('获取人员列表失败,使用模拟数据');
|
message.error('获取人员列表失败,使用模拟数据');
|
||||||
|
|
||||||
@@ -463,124 +463,70 @@ async function fetchResources(): Promise<Resource[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 数字范围筛选下拉
|
// 数字范围筛选下拉
|
||||||
|
function NumberRangeFilterDropdown({ setSelectedKeys, selectedKeys, confirm, clearFilters }: FilterDropdownProps) {
|
||||||
|
const [min, max] = String(selectedKeys?.[0] ?? ':').split(':');
|
||||||
|
const [localMin, setLocalMin] = React.useState<number | undefined>(min ? Number(min) : undefined);
|
||||||
|
const [localMax, setLocalMax] = React.useState<number | undefined>(max ? Number(max) : undefined);
|
||||||
|
return (
|
||||||
|
<div style={{ padding: 8 }}>
|
||||||
|
<Space direction="vertical" style={{ width: 200 }}>
|
||||||
|
<InputNumber placeholder="最小值" value={localMin} onChange={(v) => setLocalMin(v ?? undefined)} style={{ width: '100%' }} />
|
||||||
|
<InputNumber placeholder="最大值" value={localMax} onChange={(v) => setLocalMax(v ?? undefined)} style={{ width: '100%' }} />
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={() => { const key = `${localMin ?? ''}:${localMax ?? ''}`; setSelectedKeys?.([key]); confirm?.({ closeDropdown: true }); }}>筛选</Button>
|
||||||
|
<Button size="small" onClick={() => { setLocalMin(undefined); setLocalMax(undefined); setSelectedKeys?.([]); clearFilters?.(); confirm?.({ closeDropdown: true }); }}>重置</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildNumberRangeFilter(dataIndex: keyof Resource, label: string): ColumnType<Resource> {
|
function buildNumberRangeFilter(dataIndex: keyof Resource, label: string): ColumnType<Resource> {
|
||||||
return {
|
return {
|
||||||
title: label,
|
title: label,
|
||||||
dataIndex,
|
dataIndex,
|
||||||
sorter: (a: Resource, b: Resource) => Number((a as any)[dataIndex] ?? 0) - Number((b as any)[dataIndex] ?? 0),
|
sorter: (a: Resource, b: Resource) => {
|
||||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: FilterDropdownProps) => {
|
const av = a[dataIndex] as number | undefined;
|
||||||
const [min, max] = String(selectedKeys?.[0] ?? ':').split(':');
|
const bv = b[dataIndex] as number | undefined;
|
||||||
const [localMin, setLocalMin] = React.useState<number | undefined>(min ? Number(min) : undefined);
|
return Number(av ?? 0) - Number(bv ?? 0);
|
||||||
const [localMax, setLocalMax] = React.useState<number | undefined>(max ? Number(max) : undefined);
|
|
||||||
return (
|
|
||||||
<div style={{ padding: 8 }}>
|
|
||||||
<Space direction="vertical" style={{ width: 200 }}>
|
|
||||||
<InputNumber
|
|
||||||
placeholder="最小值"
|
|
||||||
value={localMin}
|
|
||||||
onChange={(v) => setLocalMin(v ?? undefined)}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<InputNumber
|
|
||||||
placeholder="最大值"
|
|
||||||
value={localMax}
|
|
||||||
onChange={(v) => setLocalMax(v ?? undefined)}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
icon={<SearchOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
const key = `${localMin ?? ''}:${localMax ?? ''}`;
|
|
||||||
setSelectedKeys?.([key]);
|
|
||||||
confirm?.({ closeDropdown: true });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
筛选
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
setLocalMin(undefined);
|
|
||||||
setLocalMax(undefined);
|
|
||||||
setSelectedKeys?.([]);
|
|
||||||
clearFilters?.();
|
|
||||||
confirm?.({ closeDropdown: true });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
filterDropdown: (props) => <NumberRangeFilterDropdown {...props} />,
|
||||||
onFilter: (filterValue: React.Key | boolean, record: Resource) => {
|
onFilter: (filterValue: React.Key | boolean, record: Resource) => {
|
||||||
const [minStr, maxStr] = String(filterValue).split(':');
|
const [minStr, maxStr] = String(filterValue).split(':');
|
||||||
const min = minStr ? Number(minStr) : undefined;
|
const min = minStr ? Number(minStr) : undefined;
|
||||||
const max = maxStr ? Number(maxStr) : undefined;
|
const max = maxStr ? Number(maxStr) : undefined;
|
||||||
const val = Number((record as any)[dataIndex] ?? NaN);
|
const val = record[dataIndex] as number | undefined;
|
||||||
if (Number.isNaN(val)) return false;
|
if (val === undefined || Number.isNaN(Number(val))) return false;
|
||||||
if (min !== undefined && val < min) return false;
|
if (min !== undefined && Number(val) < min) return false;
|
||||||
if (max !== undefined && val > max) return false;
|
if (max !== undefined && Number(val) > max) return false;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
} as ColumnType<Resource>;
|
} as ColumnType<Resource>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 枚举筛选下拉(用于性别等枚举类列)
|
// 枚举筛选下拉(用于性别等枚举类列)
|
||||||
|
function EnumFilterDropdown({ setSelectedKeys, selectedKeys, confirm, clearFilters, options, label }: FilterDropdownProps & { options: Array<{ label: string; value: string }>; label: string }) {
|
||||||
|
const [val, setVal] = React.useState<string | undefined>(selectedKeys && selectedKeys[0] ? String(selectedKeys[0]) : undefined);
|
||||||
|
return (
|
||||||
|
<div className="byte-table-custom-filter" style={{ padding: 8 }}>
|
||||||
|
<Space direction="vertical" style={{ width: 200 }}>
|
||||||
|
<Select allowClear placeholder={`请选择${label}`} value={val} onChange={(v) => setVal(v)} options={options} style={{ width: '100%' }} />
|
||||||
|
<Space>
|
||||||
|
<Button type="primary" size="small" icon={<SearchOutlined />} onClick={() => { setSelectedKeys?.(val ? [val] : []); confirm?.({ closeDropdown: true }); }}>筛选</Button>
|
||||||
|
<Button size="small" onClick={() => { setVal(undefined); setSelectedKeys?.([]); clearFilters?.(); confirm?.({ closeDropdown: true }); }}>重置</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function buildEnumFilter(dataIndex: keyof Resource, label: string, options: Array<{ label: string; value: string }>): ColumnType<Resource> {
|
function buildEnumFilter(dataIndex: keyof Resource, label: string, options: Array<{ label: string; value: string }>): ColumnType<Resource> {
|
||||||
return {
|
return {
|
||||||
title: label,
|
title: label,
|
||||||
dataIndex,
|
dataIndex,
|
||||||
key: String(dataIndex),
|
key: String(dataIndex),
|
||||||
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: FilterDropdownProps) => {
|
filterDropdown: (props) => <EnumFilterDropdown {...props} options={options} label={label} />,
|
||||||
const [val, setVal] = React.useState<string | undefined>(
|
onFilter: (filterValue: React.Key | boolean, record: Resource) => String(record[dataIndex] ?? '') === String(filterValue),
|
||||||
(selectedKeys && selectedKeys[0] ? String(selectedKeys[0]) : undefined)
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="byte-table-custom-filter" style={{ padding: 8 }}>
|
|
||||||
<Space direction="vertical" style={{ width: 200 }}>
|
|
||||||
<Select
|
|
||||||
allowClear
|
|
||||||
placeholder={`请选择${label}`}
|
|
||||||
value={val}
|
|
||||||
onChange={(v) => setVal(v)}
|
|
||||||
options={options}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
<Space>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
icon={<SearchOutlined />}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedKeys?.(val ? [val] : []);
|
|
||||||
confirm?.({ closeDropdown: true });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
筛选
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
setVal(undefined);
|
|
||||||
setSelectedKeys?.([]);
|
|
||||||
clearFilters?.();
|
|
||||||
confirm?.({ closeDropdown: true });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
重置
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onFilter: (filterValue: React.Key | boolean, record: Resource) =>
|
|
||||||
String((record as any)[dataIndex]) === String(filterValue),
|
|
||||||
render: (g: string) => {
|
render: (g: string) => {
|
||||||
const color = g === '男' ? 'blue' : g === '女' ? 'magenta' : 'default';
|
const color = g === '男' ? 'blue' : g === '女' ? 'magenta' : 'default';
|
||||||
return <Tag color={color}>{g}</Tag>;
|
return <Tag color={color}>{g}</Tag>;
|
||||||
@@ -654,7 +600,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
} else {
|
} else {
|
||||||
message.error(res.error_info || '删除失败');
|
message.error(res.error_info || '删除失败');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
} finally {
|
} finally {
|
||||||
await reloadResources();
|
await reloadResources();
|
||||||
@@ -688,7 +634,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
onFilter: (filterValue: React.Key | boolean, record: Resource) => String(record.name).includes(String(filterValue)),
|
onFilter: (filterValue: React.Key | boolean, record: Resource) => String(record.name).includes(String(filterValue)),
|
||||||
render: (text: string, record: Resource) => {
|
render: (text: string, record: Resource) => {
|
||||||
// 图片图标逻辑
|
// 图片图标逻辑
|
||||||
const hasCover = record.cover && record.cover.trim() !== '';
|
const hasCover = typeof record.cover === 'string' && record.cover.trim() !== '';
|
||||||
const pictureIcon = (
|
const pictureIcon = (
|
||||||
<PictureOutlined
|
<PictureOutlined
|
||||||
style={{
|
style={{
|
||||||
@@ -696,7 +642,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
cursor: hasCover ? 'pointer' : 'default'
|
cursor: hasCover ? 'pointer' : 'default'
|
||||||
}}
|
}}
|
||||||
onClick={hasCover ? () => {
|
onClick={hasCover ? () => {
|
||||||
setCurrentImageUrl(record.cover);
|
setCurrentImageUrl(record.cover as string);
|
||||||
setImageModalVisible(true);
|
setImageModalVisible(true);
|
||||||
} : undefined}
|
} : undefined}
|
||||||
/>
|
/>
|
||||||
@@ -834,7 +780,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (_: any, record: Resource) => (
|
render: (_: unknown, record: Resource) => (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={["click"]}
|
trigger={["click"]}
|
||||||
menu={{
|
menu={{
|
||||||
@@ -952,8 +898,9 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
} else {
|
} else {
|
||||||
message.error(res.error_info || '更新失败');
|
message.error(res.error_info || '更新失败');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
if (err?.errorFields) {
|
const hasErrorFields = typeof err === 'object' && err !== null && 'errorFields' in err;
|
||||||
|
if (hasErrorFields) {
|
||||||
message.error('请完善表单后再保存');
|
message.error('请完善表单后再保存');
|
||||||
} else {
|
} else {
|
||||||
message.error('更新失败');
|
message.error('更新失败');
|
||||||
@@ -1003,7 +950,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: ({} as any)
|
: {}
|
||||||
}
|
}
|
||||||
pagination={{
|
pagination={{
|
||||||
...pagination,
|
...pagination,
|
||||||
@@ -1055,7 +1002,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
)}
|
)}
|
||||||
{record.created_at && (
|
{record.created_at && (
|
||||||
<div style={{ fontSize: '12px', color: '#999', marginTop: '12px' }}>
|
<div style={{ fontSize: '12px', color: '#999', marginTop: '12px' }}>
|
||||||
{record.created_at ? '录入于: ' + formatDate(record.created_at) : ''}
|
{record.created_at ? '录入于: ' + formatDate(Number(record.created_at)) : ''}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1064,7 +1011,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
<span>择偶要求</span>
|
<span>择偶要求</span>
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
{record.match_requirement ? (
|
{record.match_requirement ? (
|
||||||
<div>{record.match_requirement}</div>
|
<div>{String(record.match_requirement)}</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ color: '#9ca3af' }}>暂无匹配要求</div>
|
<div style={{ color: '#9ca3af' }}>暂无匹配要求</div>
|
||||||
)}
|
)}
|
||||||
@@ -1078,7 +1025,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
{record.comments && record.comments.remark ? (
|
{record.comments && record.comments.remark ? (
|
||||||
<Space>
|
<Space>
|
||||||
<Button size="small" onClick={() => {
|
<Button size="small" onClick={() => {
|
||||||
setEditingRemark({ recordId: record.id, content: record.comments.remark.content });
|
setEditingRemark({ recordId: record.id, content: record.comments?.remark?.content || '' });
|
||||||
setRemarkModalVisible(true);
|
setRemarkModalVisible(true);
|
||||||
}}>修改</Button>
|
}}>修改</Button>
|
||||||
<Button size="small" danger onClick={async () => {
|
<Button size="small" danger onClick={async () => {
|
||||||
@@ -1090,7 +1037,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
} else {
|
} else {
|
||||||
message.error(res.error_info || '清空失败');
|
message.error(res.error_info || '清空失败');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
message.error('清空失败');
|
message.error('清空失败');
|
||||||
}
|
}
|
||||||
}}>清空</Button>
|
}}>清空</Button>
|
||||||
@@ -1121,7 +1068,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
<InputDrawer
|
<InputDrawer
|
||||||
open={inputOpen}
|
open={inputOpen}
|
||||||
onClose={onCloseInput || (() => {})}
|
onClose={onCloseInput || (() => {})}
|
||||||
onResult={(list: any) => {
|
onResult={(list: unknown) => {
|
||||||
// setInputResult(list);
|
// setInputResult(list);
|
||||||
const mapped = transformPeoples(Array.isArray(list) ? list : []);
|
const mapped = transformPeoples(Array.isArray(list) ? list : []);
|
||||||
setData(mapped);
|
setData(mapped);
|
||||||
@@ -1186,13 +1133,14 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
} else {
|
} else {
|
||||||
message.error(res.error_info || '更新失败');
|
message.error(res.error_info || '更新失败');
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
if (err?.errorFields) {
|
const hasErrorFields = typeof err === 'object' && err !== null && 'errorFields' in err;
|
||||||
message.error('请完善表单后再确认');
|
if (hasErrorFields) {
|
||||||
} else {
|
message.error('请完善表单后再确认');
|
||||||
message.error('更新失败');
|
} else {
|
||||||
|
message.error('更新失败');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
destroyOnHidden
|
destroyOnHidden
|
||||||
okText="确认"
|
okText="确认"
|
||||||
@@ -1225,7 +1173,7 @@ const ResourceList: React.FC<Props> = ({ inputOpen = false, onCloseInput, contai
|
|||||||
} else {
|
} else {
|
||||||
message.error(res.error_info || '操作失败');
|
message.error(res.error_info || '操作失败');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch {
|
||||||
message.error('操作失败');
|
message.error('操作失败');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import LoginModal from './LoginModal';
|
import LoginModal from './LoginModal';
|
||||||
import RegisterModal from './RegisterModal';
|
import RegisterModal from './RegisterModal';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/useAuth';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Layout, Menu, Grid, Drawer, Button } from 'antd';
|
import { Layout, Menu, Grid, Drawer, Button } from 'antd';
|
||||||
import { FormOutlined, UnorderedListOutlined, MenuOutlined, CopyOutlined, UserOutlined, SettingOutlined } from '@ant-design/icons';
|
import { FormOutlined, UnorderedListOutlined, MenuOutlined, CopyOutlined, UserOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import { Form, Input, Button, message, Card, Space, Upload, Modal } from 'antd'
|
import { Form, Input, Button, message, Card, Space, Upload, Modal } from 'antd'
|
||||||
import 'react-image-crop/dist/ReactCrop.css'
|
import 'react-image-crop/dist/ReactCrop.css'
|
||||||
import ReactCrop, { centerCrop, makeAspectCrop, type Crop } from 'react-image-crop'
|
import ReactCrop, { centerCrop, makeAspectCrop, type Crop } from 'react-image-crop'
|
||||||
import { useAuth } from '../contexts/AuthContext'
|
import { useAuth } from '../contexts/useAuth'
|
||||||
import { updateMe, deleteUser, uploadAvatar, updatePhone, updateEmail } from '../apis'
|
import { updateMe, deleteUser, uploadAvatar, updatePhone, updateEmail } from '../apis'
|
||||||
import { UserOutlined, EditOutlined } from '@ant-design/icons'
|
import { UserOutlined, EditOutlined } from '@ant-design/icons'
|
||||||
import LoginModal from './LoginModal';
|
import LoginModal from './LoginModal';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createContext, useContext, useState, useEffect } from 'react';
|
import { createContext, useState, useEffect } from 'react';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { login as apiLogin, register as apiRegister, sendCode as apiSendCode, getMe as apiGetMe, logout as apiLogout } from '../apis';
|
import { login as apiLogin, register as apiRegister, sendCode as apiSendCode, getMe as apiGetMe, logout as apiLogout } from '../apis';
|
||||||
import type { LoginRequest, RegisterRequest, User, SendCodeRequest } from '../apis/types';
|
import type { LoginRequest, RegisterRequest, User, SendCodeRequest } from '../apis/types';
|
||||||
@@ -28,9 +28,10 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
if (response.data) {
|
if (response.data) {
|
||||||
setUser(response.data);
|
setUser(response.data);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
setToken(null);
|
setToken(null);
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
|
void 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
validateSession();
|
validateSession();
|
||||||
@@ -48,7 +49,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
if (me.data) {
|
if (me.data) {
|
||||||
setUser(me.data);
|
setUser(me.data);
|
||||||
}
|
}
|
||||||
} catch (e) { void e; }
|
} catch {
|
||||||
|
void 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
@@ -79,7 +82,9 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
if (me.data) {
|
if (me.data) {
|
||||||
setUser(me.data);
|
setUser(me.data);
|
||||||
}
|
}
|
||||||
} catch (e) { void e; }
|
} catch {
|
||||||
|
void 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
@@ -97,10 +102,4 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAuth = () => {
|
export default AuthContext;
|
||||||
const context = useContext(AuthContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error('useAuth must be used within an AuthProvider');
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
12
src/contexts/useAuth.ts
Normal file
12
src/contexts/useAuth.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import AuthContext from './AuthContext';
|
||||||
|
|
||||||
|
export const useAuth = () => {
|
||||||
|
const context = useContext(AuthContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAuth;
|
||||||
Reference in New Issue
Block a user