add
This commit is contained in:
parent
2b57515c31
commit
f966001505
|
@ -1,4 +1,19 @@
|
||||||
import { Controller, Headers, Post, Body, UseGuards, Get, Req, HttpException, HttpStatus, BadRequestException, InternalServerErrorException, NotFoundException, UnauthorizedException, Logger } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Headers,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
UseGuards,
|
||||||
|
Get,
|
||||||
|
Req,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
BadRequestException,
|
||||||
|
InternalServerErrorException,
|
||||||
|
NotFoundException,
|
||||||
|
UnauthorizedException,
|
||||||
|
Logger,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
import { AuthSchema, JwtPayload } from '@nice/common';
|
import { AuthSchema, JwtPayload } from '@nice/common';
|
||||||
import { AuthGuard } from './auth.guard';
|
import { AuthGuard } from './auth.guard';
|
||||||
|
@ -7,8 +22,8 @@ import { z } from 'zod';
|
||||||
import { FileValidationErrorType } from './types';
|
import { FileValidationErrorType } from './types';
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
private logger = new Logger(AuthController.name)
|
private logger = new Logger(AuthController.name);
|
||||||
constructor(private readonly authService: AuthService) { }
|
constructor(private readonly authService: AuthService) {}
|
||||||
@Get('file')
|
@Get('file')
|
||||||
async authFileRequset(
|
async authFileRequset(
|
||||||
@Headers('x-original-uri') originalUri: string,
|
@Headers('x-original-uri') originalUri: string,
|
||||||
|
@ -18,7 +33,6 @@ export class AuthController {
|
||||||
@Headers('host') host: string,
|
@Headers('host') host: string,
|
||||||
@Headers('authorization') authorization: string,
|
@Headers('authorization') authorization: string,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileRequest = {
|
const fileRequest = {
|
||||||
originalUri,
|
originalUri,
|
||||||
|
@ -26,10 +40,11 @@ export class AuthController {
|
||||||
method,
|
method,
|
||||||
queryParams,
|
queryParams,
|
||||||
host,
|
host,
|
||||||
authorization
|
authorization,
|
||||||
};
|
};
|
||||||
|
|
||||||
const authResult = await this.authService.validateFileRequest(fileRequest);
|
const authResult =
|
||||||
|
await this.authService.validateFileRequest(fileRequest);
|
||||||
if (!authResult.isValid) {
|
if (!authResult.isValid) {
|
||||||
// 使用枚举类型进行错误处理
|
// 使用枚举类型进行错误处理
|
||||||
switch (authResult.error) {
|
switch (authResult.error) {
|
||||||
|
@ -41,7 +56,9 @@ export class AuthController {
|
||||||
case FileValidationErrorType.INVALID_TOKEN:
|
case FileValidationErrorType.INVALID_TOKEN:
|
||||||
throw new UnauthorizedException(authResult.error);
|
throw new UnauthorizedException(authResult.error);
|
||||||
default:
|
default:
|
||||||
throw new InternalServerErrorException(authResult.error || FileValidationErrorType.UNKNOWN_ERROR);
|
throw new InternalServerErrorException(
|
||||||
|
authResult.error || FileValidationErrorType.UNKNOWN_ERROR,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -51,17 +68,20 @@ export class AuthController {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.verbose(`File request auth failed from ${realIp} reason:${error.message}`)
|
this.logger.verbose(
|
||||||
|
`File request auth failed from ${realIp} reason:${error.message}`,
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
@Get('user-profile')
|
@Get('user-profile')
|
||||||
async getUserProfile(@Req() request: Request) {
|
async getUserProfile(@Req() request: Request) {
|
||||||
|
|
||||||
const payload: JwtPayload = (request as any).user;
|
const payload: JwtPayload = (request as any).user;
|
||||||
const { staff } = await UserProfileService.instance.getUserProfileById(payload.sub);
|
const { staff } = await UserProfileService.instance.getUserProfileById(
|
||||||
return staff
|
payload.sub,
|
||||||
|
);
|
||||||
|
return staff;
|
||||||
}
|
}
|
||||||
@Post('login')
|
@Post('login')
|
||||||
async login(@Body() body: z.infer<typeof AuthSchema.signInRequset>) {
|
async login(@Body() body: z.infer<typeof AuthSchema.signInRequset>) {
|
||||||
|
|
|
@ -146,6 +146,9 @@ export class AuthService {
|
||||||
showname,
|
showname,
|
||||||
photoUrl,
|
photoUrl,
|
||||||
deptId,
|
deptId,
|
||||||
|
office,
|
||||||
|
email,
|
||||||
|
rank,
|
||||||
...others
|
...others
|
||||||
} = data;
|
} = data;
|
||||||
const existingUser = await db.staff.findFirst({
|
const existingUser = await db.staff.findFirst({
|
||||||
|
@ -175,6 +178,9 @@ export class AuthService {
|
||||||
// domainId: data.deptId,
|
// domainId: data.deptId,
|
||||||
meta: {
|
meta: {
|
||||||
photoUrl,
|
photoUrl,
|
||||||
|
office,
|
||||||
|
rank,
|
||||||
|
email,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,14 +2,21 @@ import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader";
|
||||||
import DepartmentSelect from "@web/src/components/models/department/department-select";
|
import DepartmentSelect from "@web/src/components/models/department/department-select";
|
||||||
import { Form, Input, Button, Select } from "antd";
|
import { Form, Input, Button, Select } from "antd";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export interface RegisterFormData {
|
export interface RegisterFormData {
|
||||||
deptId: string;
|
deptId: string;
|
||||||
|
domainId: string;
|
||||||
username: string;
|
username: string;
|
||||||
showname: string;
|
showname: string;
|
||||||
|
|
||||||
officerId: string;
|
officerId: string;
|
||||||
password: string;
|
password: string;
|
||||||
repeatPass: string;
|
repeatPass: string;
|
||||||
|
rank: string;
|
||||||
|
office: string;
|
||||||
|
email: string;
|
||||||
|
phoneNumber: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RegisterFormProps {
|
interface RegisterFormProps {
|
||||||
|
@ -19,7 +26,7 @@ interface RegisterFormProps {
|
||||||
|
|
||||||
export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
|
export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
|
||||||
const [form] = Form.useForm<RegisterFormData>();
|
const [form] = Form.useForm<RegisterFormData>();
|
||||||
|
const [domainId, setDomainId] = useState<string>();
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
|
@ -34,10 +41,11 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
|
||||||
<div>
|
<div>
|
||||||
<Form.Item name="photoUrl" label="头像" noStyle>
|
<Form.Item name="photoUrl" label="头像" noStyle>
|
||||||
<AvatarUploader
|
<AvatarUploader
|
||||||
|
className="rounded-lg"
|
||||||
placeholder="点击上传头像"
|
placeholder="点击上传头像"
|
||||||
style={{
|
style={{
|
||||||
height: 120,
|
height: 150,
|
||||||
width: 100,
|
width: 120,
|
||||||
}}></AvatarUploader>
|
}}></AvatarUploader>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,67 +70,126 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
|
||||||
]}>
|
]}>
|
||||||
<Input placeholder="姓名" />
|
<Input placeholder="姓名" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
name={"domainId"}
|
||||||
|
label="所属域"
|
||||||
|
rules={[{ required: true }]}>
|
||||||
|
<DepartmentSelect
|
||||||
|
placeholder="选择域"
|
||||||
|
onChange={(value) => {
|
||||||
|
setDomainId(value as string);
|
||||||
|
}}
|
||||||
|
domain={true}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="deptId"
|
name="deptId"
|
||||||
noStyle
|
noStyle
|
||||||
label="部门"
|
label="部门"
|
||||||
rules={[{ required: true, message: "请选择部门" }]}>
|
rules={[{ required: true, message: "请选择部门" }]}>
|
||||||
<DepartmentSelect></DepartmentSelect>
|
<DepartmentSelect
|
||||||
|
rootId={domainId}></DepartmentSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-2 flex-1 mb-2">
|
||||||
<Form.Item
|
<Form.Item noStyle name={"rank"}>
|
||||||
name="officerId"
|
<Input
|
||||||
label="证件号"
|
placeholder="请输入职级(可选)"
|
||||||
rules={[
|
autoComplete="off"
|
||||||
{ required: true, message: "请输入证件号" },
|
spellCheck={false}
|
||||||
{
|
allowClear
|
||||||
pattern: /^\d{5,12}$/,
|
/>
|
||||||
message: "请输入有效的证件号(5-12位数字)",
|
</Form.Item>
|
||||||
},
|
<Form.Item
|
||||||
]}>
|
name="officerId"
|
||||||
<Input placeholder="证件号" />
|
label="证件号"
|
||||||
</Form.Item>
|
noStyle
|
||||||
|
rules={[
|
||||||
<Form.Item
|
{ required: true, message: "请输入证件号" },
|
||||||
name="password"
|
{
|
||||||
label="密码"
|
pattern: /^\d{5,12}$/,
|
||||||
rules={[
|
message: "请输入有效的证件号(5-12位数字)",
|
||||||
{ required: true, message: "请输入密码" },
|
|
||||||
{ min: 8, message: "密码至少需要8个字符" },
|
|
||||||
{
|
|
||||||
pattern:
|
|
||||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
|
|
||||||
message: "密码必须包含大小写字母、数字和特殊字符",
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<Input.Password placeholder="密码" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
name="repeatPass"
|
|
||||||
label="确认密码"
|
|
||||||
dependencies={["password"]}
|
|
||||||
rules={[
|
|
||||||
{ required: true, message: "请确认密码" },
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(_, value) {
|
|
||||||
if (
|
|
||||||
!value ||
|
|
||||||
getFieldValue("password") === value
|
|
||||||
) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
return Promise.reject(
|
|
||||||
new Error("两次输入的密码不一致")
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}),
|
]}>
|
||||||
]}>
|
<Input placeholder="证件号(可选)" />
|
||||||
<Input.Password placeholder="确认密码" />
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
pattern: /^\d{6,11}$/,
|
||||||
|
message: "请输入正确的手机号(数字)",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
name={"phoneNumber"}
|
||||||
|
label="手机号">
|
||||||
|
<Input
|
||||||
|
autoComplete="new-phone" // 使用非标准的自动完成值
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
placeholder="请输入手机号(可选)"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={"email"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入邮箱(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={"office"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入办公室地点(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="password"
|
||||||
|
label="密码"
|
||||||
|
noStyle
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "请输入密码" },
|
||||||
|
{ min: 8, message: "密码至少需要8个字符" },
|
||||||
|
{
|
||||||
|
pattern:
|
||||||
|
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
|
||||||
|
message:
|
||||||
|
"密码必须包含大小写字母、数字和特殊字符",
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Input.Password placeholder="密码" />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="repeatPass"
|
||||||
|
label="确认密码"
|
||||||
|
noStyle
|
||||||
|
dependencies={["password"]}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: "请确认密码" },
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (
|
||||||
|
!value ||
|
||||||
|
getFieldValue("password") === value
|
||||||
|
) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(
|
||||||
|
new Error("两次输入的密码不一致")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}>
|
||||||
|
<Input.Password placeholder="确认密码" />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default function WriteLetterPage() {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
order: "desc",
|
order: "asc",
|
||||||
},
|
},
|
||||||
// orderBy:{
|
// orderBy:{
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
||||||
{/* 正在上传的文件 */}
|
{/* 正在上传的文件 */}
|
||||||
{(uploadingFiles.length > 0 || completedFiles.length > 0) && (
|
{(uploadingFiles.length > 0 || completedFiles.length > 0) && (
|
||||||
<div className=" p-2 border rounded bg-white mt-1">
|
<div className=" px-2 py-0 border rounded bg-white mt-1 ">
|
||||||
{uploadingFiles.map((file) => (
|
{uploadingFiles.map((file) => (
|
||||||
<div
|
<div
|
||||||
key={file.fileKey}
|
key={file.fileKey}
|
||||||
|
@ -177,7 +177,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
completedFiles.map((file, index) => (
|
completedFiles.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center justify-between gap-2 py-2">
|
className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircleOutlined className="text-green-500" />
|
<CheckCircleOutlined className="text-green-500" />
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
|
@ -188,10 +188,12 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
type="text"
|
type="text"
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={() =>
|
onClick={(e) => {
|
||||||
file.fileId &&
|
e.stopPropagation(); // 阻止事件冒泡
|
||||||
handleRemoveFile(file.fileId)
|
if (file.fileId) {
|
||||||
}
|
handleRemoveFile(file.fileId); // 只删除文件
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Button, Drawer, Modal } from "antd";
|
||||||
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { UserEditorContext } from "./usermenu";
|
||||||
|
import UserForm from "./user-form";
|
||||||
|
|
||||||
|
export default function UserEditModal() {
|
||||||
|
const { formLoading, modalOpen, setModalOpen, form } =
|
||||||
|
useContext(UserEditorContext);
|
||||||
|
const handleOk = () => {
|
||||||
|
form.submit();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width={400}
|
||||||
|
onOk={handleOk}
|
||||||
|
open={modalOpen}
|
||||||
|
confirmLoading={formLoading}
|
||||||
|
onCancel={() => {
|
||||||
|
setModalOpen(false);
|
||||||
|
}}
|
||||||
|
title={"编辑个人信息"}>
|
||||||
|
<UserForm />
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
import { Button, Form, Input, Spin, Switch, message } from "antd";
|
||||||
|
import { useContext, useEffect } from "react";
|
||||||
|
import { useStaff } from "@nice/client";
|
||||||
|
import DepartmentSelect from "@web/src/components/models/department/department-select";
|
||||||
|
import { api } from "@nice/client";
|
||||||
|
|
||||||
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
|
import AvatarUploader from "@web/src/components/common/uploader/AvatarUploader";
|
||||||
|
import { StaffDto } from "@nice/common";
|
||||||
|
import { UserEditorContext } from "./usermenu";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
export default function StaffForm() {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const { create, update } = useStaff(); // Ensure you have these methods in your hooks
|
||||||
|
const {
|
||||||
|
formLoading,
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
domainId,
|
||||||
|
setDomainId,
|
||||||
|
form,
|
||||||
|
setFormLoading,
|
||||||
|
} = useContext(UserEditorContext);
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
data: StaffDto;
|
||||||
|
isLoading: boolean;
|
||||||
|
} = api.staff.findFirst.useQuery(
|
||||||
|
{ where: { id: user?.id } },
|
||||||
|
{ enabled: !!user?.id }
|
||||||
|
);
|
||||||
|
const { isRoot } = useAuth();
|
||||||
|
async function handleFinish(values: any) {
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
showname,
|
||||||
|
deptId,
|
||||||
|
domainId,
|
||||||
|
password,
|
||||||
|
phoneNumber,
|
||||||
|
officerId,
|
||||||
|
enabled,
|
||||||
|
photoUrl,
|
||||||
|
email,
|
||||||
|
rank,
|
||||||
|
office,
|
||||||
|
} = values;
|
||||||
|
setFormLoading(true);
|
||||||
|
try {
|
||||||
|
if (data && user?.id) {
|
||||||
|
await update.mutateAsync({
|
||||||
|
where: { id: data.id },
|
||||||
|
data: {
|
||||||
|
username,
|
||||||
|
deptId,
|
||||||
|
showname,
|
||||||
|
domainId,
|
||||||
|
password,
|
||||||
|
phoneNumber,
|
||||||
|
officerId,
|
||||||
|
enabled,
|
||||||
|
meta: {
|
||||||
|
photoUrl,
|
||||||
|
email,
|
||||||
|
rank,
|
||||||
|
office,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toast.success("提交成功");
|
||||||
|
setModalOpen(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.error(err.message);
|
||||||
|
} finally {
|
||||||
|
setFormLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
form.resetFields();
|
||||||
|
if (data) {
|
||||||
|
form.setFieldValue("username", data.username);
|
||||||
|
form.setFieldValue("showname", data.showname);
|
||||||
|
form.setFieldValue("domainId", data.domainId);
|
||||||
|
form.setFieldValue("deptId", data.deptId);
|
||||||
|
form.setFieldValue("officerId", data.officerId);
|
||||||
|
form.setFieldValue("phoneNumber", data.phoneNumber);
|
||||||
|
form.setFieldValue("enabled", data.enabled);
|
||||||
|
form.setFieldValue("photoUrl", data?.meta?.photoUrl);
|
||||||
|
form.setFieldValue("email", data?.meta?.email);
|
||||||
|
form.setFieldValue("rank", data?.meta?.rank);
|
||||||
|
form.setFieldValue("office", data?.meta?.office);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (!data && domainId) {
|
||||||
|
// form.setFieldValue("domainId", domainId);
|
||||||
|
// form.setFieldValue("deptId", domainId);
|
||||||
|
// }
|
||||||
|
// }, [domainId, data as any]);
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute h-full inset-0 flex items-center justify-center bg-white bg-opacity-50 z-10">
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Form
|
||||||
|
disabled={isLoading}
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
requiredMark="optional"
|
||||||
|
autoComplete="off"
|
||||||
|
onFinish={handleFinish}>
|
||||||
|
<div className=" flex items-center gap-4 mb-2">
|
||||||
|
<div>
|
||||||
|
<Form.Item name={"photoUrl"} label="头像" noStyle>
|
||||||
|
<AvatarUploader
|
||||||
|
placeholder="点击上传头像"
|
||||||
|
className="rounded-lg"
|
||||||
|
style={{
|
||||||
|
width: "120px",
|
||||||
|
height: "150px",
|
||||||
|
}}></AvatarUploader>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-2 flex-1">
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
name={"username"}
|
||||||
|
label="帐号">
|
||||||
|
<Input
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
allowClear
|
||||||
|
autoComplete="new-username" // 使用非标准的自动完成值
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
rules={[{ required: true }]}
|
||||||
|
name={"showname"}
|
||||||
|
label="姓名">
|
||||||
|
<Input
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
allowClear
|
||||||
|
autoComplete="new-name" // 使用非标准的自动完成值
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={"domainId"}
|
||||||
|
label="所属域"
|
||||||
|
noStyle
|
||||||
|
rules={[{ required: true }]}>
|
||||||
|
<DepartmentSelect
|
||||||
|
placeholder="选择域"
|
||||||
|
onChange={(value) => {
|
||||||
|
setDomainId(value as string);
|
||||||
|
}}
|
||||||
|
domain={true}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
name={"deptId"}
|
||||||
|
label="所属单位"
|
||||||
|
rules={[{ required: true }]}>
|
||||||
|
<DepartmentSelect rootId={domainId} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-2 flex-1">
|
||||||
|
<Form.Item noStyle name={"rank"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入职级(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
pattern: /^\d{5,18}$/,
|
||||||
|
message: "请输入正确的证件号(数字)",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
noStyle
|
||||||
|
name={"officerId"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入证件号(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
pattern: /^\d{6,11}$/,
|
||||||
|
message: "请输入正确的手机号(数字)",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
noStyle
|
||||||
|
name={"phoneNumber"}
|
||||||
|
label="手机号">
|
||||||
|
<Input
|
||||||
|
placeholder="请输入手机号(可选)"
|
||||||
|
autoComplete="new-phone" // 使用非标准的自动完成值
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={"email"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入邮箱(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={"office"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入办公室地点(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle label="密码" name={"password"}>
|
||||||
|
<Input.Password
|
||||||
|
placeholder="修改密码"
|
||||||
|
spellCheck={false}
|
||||||
|
visibilityToggle
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,18 +1,25 @@
|
||||||
import { useClickOutside } from "@web/src/hooks/useClickOutside";
|
import { useClickOutside } from "@web/src/hooks/useClickOutside";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import React,{ useState, useRef, useCallback, useMemo, createContext } from "react";
|
import React, {
|
||||||
import { Avatar } from "../../common/element/Avatar";
|
useState,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
createContext,
|
||||||
|
} from "react";
|
||||||
|
import { Avatar } from "../../../common/element/Avatar";
|
||||||
import {
|
import {
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
QuestionCircleOutlined,
|
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { FormInstance, Spin } from "antd";
|
import { FormInstance, Spin } from "antd";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { MenuItemType } from "./types";
|
import { MenuItemType } from "../types";
|
||||||
import { RolePerms } from "@nice/common";
|
import { RolePerms } from "@nice/common";
|
||||||
|
import { useForm } from "antd/es/form/Form";
|
||||||
|
import UserEditModal from "./user-edit-modal";
|
||||||
const menuVariants = {
|
const menuVariants = {
|
||||||
hidden: { opacity: 0, scale: 0.95, y: -10 },
|
hidden: { opacity: 0, scale: 0.95, y: -10 },
|
||||||
visible: {
|
visible: {
|
||||||
|
@ -36,36 +43,33 @@ const menuVariants = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserEditorContext = createContext<{
|
export const UserEditorContext = createContext<{
|
||||||
domainId: string;
|
domainId: string;
|
||||||
modalOpen: boolean;
|
setDomainId: React.Dispatch<React.SetStateAction<string>>;
|
||||||
setDomainId: React.Dispatch<React.SetStateAction<string>>;
|
modalOpen: boolean;
|
||||||
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
editId: string;
|
form: FormInstance<any>;
|
||||||
setEditId: React.Dispatch<React.SetStateAction<string>>;
|
formLoading: boolean;
|
||||||
form: FormInstance<any>;
|
setFormLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
formLoading: boolean;
|
|
||||||
setFormLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
canManageAnyStaff: boolean;
|
|
||||||
}>({
|
}>({
|
||||||
domainId: undefined,
|
modalOpen: false,
|
||||||
modalOpen: false,
|
domainId: undefined,
|
||||||
setDomainId: undefined,
|
setDomainId: undefined,
|
||||||
setModalOpen: undefined,
|
setModalOpen: undefined,
|
||||||
editId: undefined,
|
form: undefined,
|
||||||
setEditId: undefined,
|
formLoading: undefined,
|
||||||
form: undefined,
|
setFormLoading: undefined,
|
||||||
formLoading: undefined,
|
|
||||||
setFormLoading: undefined,
|
|
||||||
canManageAnyStaff: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function UserMenu() {
|
export function UserMenu() {
|
||||||
|
const [form] = useForm();
|
||||||
|
const [formLoading, setFormLoading] = useState<boolean>();
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
const { user, logout, isLoading, hasSomePermissions } = useAuth();
|
const { user, logout, isLoading, hasSomePermissions } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
useClickOutside(menuRef, () => setShowMenu(false));
|
useClickOutside(menuRef, () => setShowMenu(false));
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||||
|
const [domainId, setDomainId] = useState<string>();
|
||||||
const toggleMenu = useCallback(() => {
|
const toggleMenu = useCallback(() => {
|
||||||
setShowMenu((prev) => !prev);
|
setShowMenu((prev) => !prev);
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -78,7 +82,9 @@ export function UserMenu() {
|
||||||
{
|
{
|
||||||
icon: <UserOutlined className="text-lg" />,
|
icon: <UserOutlined className="text-lg" />,
|
||||||
label: "个人信息",
|
label: "个人信息",
|
||||||
action: () => {},
|
action: () => {
|
||||||
|
setModalOpen(true);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
canManageAnyStaff && {
|
canManageAnyStaff && {
|
||||||
icon: <SettingOutlined className="text-lg" />,
|
icon: <SettingOutlined className="text-lg" />,
|
||||||
|
@ -115,7 +121,16 @@ export function UserMenu() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<UserEditorContext.Provider
|
||||||
|
value={{
|
||||||
|
formLoading,
|
||||||
|
setFormLoading,
|
||||||
|
form,
|
||||||
|
domainId,
|
||||||
|
modalOpen,
|
||||||
|
setDomainId,
|
||||||
|
setModalOpen,
|
||||||
|
}}>
|
||||||
<div ref={menuRef} className="relative">
|
<div ref={menuRef} className="relative">
|
||||||
<motion.button
|
<motion.button
|
||||||
aria-label="用户菜单"
|
aria-label="用户菜单"
|
||||||
|
@ -238,6 +253,7 @@ export function UserMenu() {
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
<UserEditModal></UserEditModal>
|
||||||
|
</UserEditorContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import { SearchBar } from "./SearchBar";
|
||||||
import Navigation from "./navigation";
|
import Navigation from "./navigation";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { UserOutlined } from "@ant-design/icons";
|
import { UserOutlined } from "@ant-design/icons";
|
||||||
import { UserMenu } from "../element/usermenu";
|
import { UserMenu } from "../element/usermenu/usermenu";
|
||||||
import SineWavesCanvas from "../../animation/sine-wave";
|
import SineWavesCanvas from "../../animation/sine-wave";
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
onSearch?: (query: string) => void;
|
onSearch?: (query: string) => void;
|
||||||
|
|
|
@ -81,7 +81,7 @@ export function useNavItem() {
|
||||||
...categoryItems,
|
...categoryItems,
|
||||||
// staticItems.help,
|
// staticItems.help,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}, [data]);
|
}, [data, user]);
|
||||||
|
|
||||||
return { navItems };
|
return { navItems };
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ export function LetterBasicForm() {
|
||||||
<Select
|
<Select
|
||||||
mode="tags"
|
mode="tags"
|
||||||
showSearch={false}
|
showSearch={false}
|
||||||
|
suffixIcon={null}
|
||||||
placeholder="输入标签后按回车添加"
|
placeholder="输入标签后按回车添加"
|
||||||
value={form.getFieldValue(["meta", "tags"]) || []}
|
value={form.getFieldValue(["meta", "tags"]) || []}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
|
@ -54,16 +55,6 @@ export function LetterBasicForm() {
|
||||||
tokenSeparators={[",", " "]}
|
tokenSeparators={[",", " "]}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
dropdownStyle={{ display: "none" }}
|
dropdownStyle={{ display: "none" }}
|
||||||
tagRender={({ label, onClose }) => (
|
|
||||||
<div className="bg-primary-50 text-primary-600 px-3 py-1 rounded-full text-sm mr-2 mb-1 flex items-center">
|
|
||||||
{label}
|
|
||||||
<span
|
|
||||||
className="ml-2 cursor-pointer hover:text-primary-700"
|
|
||||||
onClick={onClose}>
|
|
||||||
×
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|
|
@ -34,11 +34,11 @@ export const StaffEditorContext = createContext<{
|
||||||
});
|
});
|
||||||
export default function StaffEditor() {
|
export default function StaffEditor() {
|
||||||
const [form] = useForm();
|
const [form] = useForm();
|
||||||
|
const [formLoading, setFormLoading] = useState<boolean>();
|
||||||
const [domainId, setDomainId] = useState<string>();
|
const [domainId, setDomainId] = useState<string>();
|
||||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||||
const [editId, setEditId] = useState<string>();
|
const [editId, setEditId] = useState<string>();
|
||||||
const { user, hasSomePermissions } = useAuth();
|
const { user, hasSomePermissions } = useAuth();
|
||||||
const [formLoading, setFormLoading] = useState<boolean>();
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
setDomainId(user.domainId);
|
setDomainId(user.domainId);
|
||||||
|
|
|
@ -41,6 +41,9 @@ export default function StaffForm() {
|
||||||
officerId,
|
officerId,
|
||||||
enabled,
|
enabled,
|
||||||
photoUrl,
|
photoUrl,
|
||||||
|
email,
|
||||||
|
rank,
|
||||||
|
office,
|
||||||
} = values;
|
} = values;
|
||||||
setFormLoading(true);
|
setFormLoading(true);
|
||||||
try {
|
try {
|
||||||
|
@ -58,6 +61,9 @@ export default function StaffForm() {
|
||||||
enabled,
|
enabled,
|
||||||
meta: {
|
meta: {
|
||||||
photoUrl,
|
photoUrl,
|
||||||
|
email,
|
||||||
|
rank,
|
||||||
|
office,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -73,6 +79,9 @@ export default function StaffForm() {
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
meta: {
|
meta: {
|
||||||
photoUrl,
|
photoUrl,
|
||||||
|
email,
|
||||||
|
rank,
|
||||||
|
office,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -100,6 +109,9 @@ export default function StaffForm() {
|
||||||
form.setFieldValue("phoneNumber", data.phoneNumber);
|
form.setFieldValue("phoneNumber", data.phoneNumber);
|
||||||
form.setFieldValue("enabled", data.enabled);
|
form.setFieldValue("enabled", data.enabled);
|
||||||
form.setFieldValue("photoUrl", data?.meta?.photoUrl);
|
form.setFieldValue("photoUrl", data?.meta?.photoUrl);
|
||||||
|
form.setFieldValue("email", data?.meta?.email);
|
||||||
|
form.setFieldValue("rank", data?.meta?.rank);
|
||||||
|
form.setFieldValue("office", data?.meta?.office);
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -126,9 +138,11 @@ export default function StaffForm() {
|
||||||
<div>
|
<div>
|
||||||
<Form.Item name={"photoUrl"} label="头像" noStyle>
|
<Form.Item name={"photoUrl"} label="头像" noStyle>
|
||||||
<AvatarUploader
|
<AvatarUploader
|
||||||
|
placeholder="点击上传头像"
|
||||||
|
className="rounded-lg"
|
||||||
style={{
|
style={{
|
||||||
width: "100px",
|
width: "120px",
|
||||||
height: "120px",
|
height: "150px",
|
||||||
}}></AvatarUploader>
|
}}></AvatarUploader>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
|
@ -157,6 +171,17 @@ export default function StaffForm() {
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
name={"domainId"}
|
||||||
|
label="所属域"
|
||||||
|
rules={[{ required: true }]}>
|
||||||
|
<DepartmentSelect
|
||||||
|
placeholder="选择域"
|
||||||
|
rootId={isRoot ? undefined : domainId}
|
||||||
|
domain={true}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
noStyle
|
noStyle
|
||||||
name={"deptId"}
|
name={"deptId"}
|
||||||
|
@ -168,58 +193,81 @@ export default function StaffForm() {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{canManageAnyStaff && (
|
<div className="grid grid-cols-1 gap-2 flex-1">
|
||||||
<Form.Item
|
<Form.Item noStyle name={"rank"}>
|
||||||
name={"domainId"}
|
<Input
|
||||||
label="所属域"
|
placeholder="请输入职级(可选)"
|
||||||
rules={[{ required: true }]}>
|
autoComplete="off"
|
||||||
<DepartmentSelect
|
spellCheck={false}
|
||||||
placeholder="选择域"
|
allowClear
|
||||||
rootId={isRoot ? undefined : domainId}
|
|
||||||
domain={true}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
<Form.Item
|
||||||
<Form.Item
|
noStyle
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
required: false,
|
||||||
pattern: /^\d{5,18}$/,
|
pattern: /^\d{5,18}$/,
|
||||||
message: "请输入正确的证件号(数字)",
|
message: "请输入正确的证件号(数字)",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
name={"officerId"}
|
name={"officerId"}
|
||||||
label="证件号">
|
label="证件号">
|
||||||
<Input autoComplete="off" spellCheck={false} allowClear />
|
<Input
|
||||||
</Form.Item>
|
autoComplete="off"
|
||||||
<Form.Item
|
spellCheck={false}
|
||||||
rules={[
|
allowClear
|
||||||
{
|
placeholder="请输入证件号(可选)"
|
||||||
required: false,
|
/>
|
||||||
pattern: /^\d{6,11}$/,
|
|
||||||
message: "请输入正确的手机号(数字)",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
name={"phoneNumber"}
|
|
||||||
label="手机号">
|
|
||||||
<Input
|
|
||||||
autoComplete="new-phone" // 使用非标准的自动完成值
|
|
||||||
spellCheck={false}
|
|
||||||
allowClear
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="密码" name={"password"}>
|
|
||||||
<Input.Password
|
|
||||||
spellCheck={false}
|
|
||||||
visibilityToggle
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
{editId && (
|
|
||||||
<Form.Item label="是否启用" name={"enabled"}>
|
|
||||||
<Switch></Switch>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
pattern: /^\d{6,11}$/,
|
||||||
|
message: "请输入正确的手机号(数字)",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
name={"phoneNumber"}
|
||||||
|
label="手机号">
|
||||||
|
<Input
|
||||||
|
autoComplete="new-phone" // 使用非标准的自动完成值
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
placeholder="请输入手机号(可选)"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={"email"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入邮箱(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={"office"}>
|
||||||
|
<Input
|
||||||
|
placeholder="请输入办公室地点(可选)"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="密码" name={"password"} noStyle>
|
||||||
|
<Input.Password
|
||||||
|
spellCheck={false}
|
||||||
|
visibilityToggle
|
||||||
|
autoComplete="new-password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
{editId && (
|
||||||
|
<Form.Item label="是否启用" name={"enabled"}>
|
||||||
|
<Switch></Switch>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface StaffSelectProps {
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
domainId?: string;
|
domainId?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
size?: SizeType
|
size?: SizeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StaffSelect({
|
export default function StaffSelect({
|
||||||
|
@ -21,7 +21,7 @@ export default function StaffSelect({
|
||||||
style,
|
style,
|
||||||
multiple,
|
multiple,
|
||||||
domainId,
|
domainId,
|
||||||
size
|
size,
|
||||||
}: StaffSelectProps) {
|
}: StaffSelectProps) {
|
||||||
const [keyword, setQuery] = useState<string>("");
|
const [keyword, setQuery] = useState<string>("");
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,10 @@ model Staff {
|
||||||
username String @unique @map("username")
|
username String @unique @map("username")
|
||||||
avatar String? @map("avatar")
|
avatar String? @map("avatar")
|
||||||
password String? @map("password")
|
password String? @map("password")
|
||||||
|
|
||||||
phoneNumber String? @unique @map("phone_number")
|
phoneNumber String? @unique @map("phone_number")
|
||||||
|
|
||||||
|
|
||||||
domainId String? @map("domain_id")
|
domainId String? @map("domain_id")
|
||||||
deptId String? @map("dept_id")
|
deptId String? @map("dept_id")
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,14 @@ export const AuthSchema = {
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
deptId: z.string().nullish(),
|
deptId: z.string().nullish(),
|
||||||
|
domainId: z.string().nullish(),
|
||||||
officerId: z.string().nullish(),
|
officerId: z.string().nullish(),
|
||||||
showname: z.string().nullish(),
|
showname: z.string().nullish(),
|
||||||
phoneNumber: z.string().nullish(),
|
phoneNumber: z.string().nullish(),
|
||||||
photoUrl: z.string().nullish(),
|
photoUrl: z.string().nullish(),
|
||||||
|
rank: z.string().nullish(),
|
||||||
|
office: z.string().nullish(),
|
||||||
|
email: z.string().nullish(),
|
||||||
}),
|
}),
|
||||||
refreshTokenRequest: z.object({
|
refreshTokenRequest: z.object({
|
||||||
refreshToken: z.string(),
|
refreshToken: z.string(),
|
||||||
|
|
Loading…
Reference in New Issue