diff --git a/src/api/axios.js b/src/api/axios.ts similarity index 100% rename from src/api/axios.js rename to src/api/axios.ts diff --git a/src/components/TeamDeleteButton.jsx b/src/components/TeamDeleteButton.tsx similarity index 100% rename from src/components/TeamDeleteButton.jsx rename to src/components/TeamDeleteButton.tsx diff --git a/src/config/DeviceStatusConfig.js b/src/config/DeviceStatusConfig.ts similarity index 100% rename from src/config/DeviceStatusConfig.js rename to src/config/DeviceStatusConfig.ts diff --git a/src/config/datePresetsConfig.js b/src/config/datePresetsConfig.ts similarity index 87% rename from src/config/datePresetsConfig.js rename to src/config/datePresetsConfig.ts index 41c54f0..529278f 100644 --- a/src/config/datePresetsConfig.js +++ b/src/config/datePresetsConfig.ts @@ -1,9 +1,9 @@ -import dayjs from "dayjs"; +import dayjs, { Dayjs } from "dayjs"; import quarterOfYear from "dayjs/plugin/quarterOfYear"; dayjs.extend(quarterOfYear); -export const datePresets = [ +export const datePresets: Array<{ label: string; value: [Dayjs, Dayjs] }> = [ { label: "本月", value: [dayjs().startOf("month"), dayjs().endOf("month")], diff --git a/src/config/menuConfig.js b/src/config/menuConfig.ts similarity index 100% rename from src/config/menuConfig.js rename to src/config/menuConfig.ts diff --git a/src/config/roleRouteConfig.js b/src/config/roleRouteConfig.ts similarity index 100% rename from src/config/roleRouteConfig.js rename to src/config/roleRouteConfig.ts diff --git a/src/features/auth/authSlice.js b/src/features/auth/authSlice.ts similarity index 95% rename from src/features/auth/authSlice.js rename to src/features/auth/authSlice.ts index 642a88f..67dec42 100644 --- a/src/features/auth/authSlice.js +++ b/src/features/auth/authSlice.ts @@ -30,7 +30,7 @@ const authSlice = createSlice({ state.roles = payload.roles; state.token = payload.token; - localStorage.setItem("userId", payload.userId); + localStorage.setItem("userId", payload.userId.toString()); localStorage.setItem("name", payload.name); localStorage.setItem("roles", JSON.stringify(payload.roles)); localStorage.setItem("token", action.payload.token); diff --git a/src/features/auth/authThunk.js b/src/features/auth/authThunk.js deleted file mode 100644 index 72ede34..0000000 --- a/src/features/auth/authThunk.js +++ /dev/null @@ -1,11 +0,0 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import axiosInstance from "../../api/axios"; - -export const login = createAsyncThunk( - "auth/login", - async (values, thunkAPI) => { - const res = await axiosInstance.post("/login", values); - - return res; - } -); diff --git a/src/features/auth/authThunk.ts b/src/features/auth/authThunk.ts new file mode 100644 index 0000000..0634a53 --- /dev/null +++ b/src/features/auth/authThunk.ts @@ -0,0 +1,15 @@ +import { createAsyncThunk } from "@reduxjs/toolkit"; +import axiosInstance from "../../api/axios"; +import { LoginResponse } from "./types"; + +export const login = createAsyncThunk( + "auth/login", + async (values) => { + const res = await axiosInstance.post( + "/login", + values + ); + + return res; + } +); diff --git a/src/features/auth/types.ts b/src/features/auth/types.ts new file mode 100644 index 0000000..e32b652 --- /dev/null +++ b/src/features/auth/types.ts @@ -0,0 +1,6 @@ +export interface LoginResponse { + userId: number; + name: string; + roles: string[]; + token: string; +} diff --git a/src/layouts/CommonLayout.jsx b/src/layouts/CommonLayout.tsx similarity index 100% rename from src/layouts/CommonLayout.jsx rename to src/layouts/CommonLayout.tsx diff --git a/src/main.jsx b/src/main.tsx similarity index 93% rename from src/main.jsx rename to src/main.tsx index 1e7f053..378caa8 100644 --- a/src/main.jsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client"; import { Provider } from "react-redux"; import { RouterProvider } from "react-router-dom"; import "./index.css"; -import router from "./router/index.jsx"; +import router from "./router/index.js"; import { store } from "./store/index.js"; import dayjs from "dayjs"; import zhCN from "antd/locale/zh_CN"; diff --git a/src/pages/Login.jsx b/src/pages/Login.tsx similarity index 95% rename from src/pages/Login.jsx rename to src/pages/Login.tsx index 3cfaaa8..a8b674b 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.tsx @@ -4,9 +4,10 @@ import { useDispatch } from "react-redux"; import { useNavigate } from "react-router-dom"; import { login } from "../features/auth/authThunk"; import roleRoute from "../config/roleRouteConfig"; +import { store } from "store"; export default function Login() { - const dispatch = useDispatch(); + const dispatch = useDispatch(); const navigate = useNavigate(); const onFinish = async (values) => { diff --git a/src/pages/admin/DeviceDetailStatsModal.jsx b/src/pages/admin/DeviceDetailStatsModal.tsx similarity index 85% rename from src/pages/admin/DeviceDetailStatsModal.jsx rename to src/pages/admin/DeviceDetailStatsModal.tsx index 984e897..d267231 100644 --- a/src/pages/admin/DeviceDetailStatsModal.jsx +++ b/src/pages/admin/DeviceDetailStatsModal.tsx @@ -1,7 +1,14 @@ -import { message, Modal, Space, Spin, Table } from "antd"; -import { useEffect, useState } from "react"; +import { message, Modal, Spin, Table } from "antd"; +import { useCallback, useEffect, useState } from "react"; import axiosInstance from "../../api/axios"; +interface DeviceDetailStats { + applicantName: string; + applicantTeam: string; + startDay: Date; + endDay: Date; +} + export default function DeviceDetailStatsModal({ visible, record, @@ -9,12 +16,15 @@ export default function DeviceDetailStatsModal({ onClose, }) { const [loading, setLoading] = useState(false); - const [data, setData] = useState([]); + const [data, setData] = useState([]); - const fetchData = async () => { + const fetchData = useCallback(async () => { setLoading(true); try { - const res = await axiosInstance.get("/device/detail-stats", { + const res = await axiosInstance.get< + DeviceDetailStats[], + DeviceDetailStats[] + >("/device/detail-stats", { params: { deviceId: record.deviceId, start: range[0].format("YYYY-MM-DD"), @@ -22,12 +32,12 @@ export default function DeviceDetailStatsModal({ }, }); setData(res); - } catch (e) { + } catch { message.error("获取数据失败"); } finally { setLoading(false); } - }; + }, [record, range]); const handleExport = async () => { setLoading(true); @@ -73,7 +83,7 @@ export default function DeviceDetailStatsModal({ if (visible) { fetchData(); } - }, [visible, record]); + }, [visible, fetchData]); const columns = [ { diff --git a/src/pages/admin/DeviceStats.jsx b/src/pages/admin/DeviceStats.tsx similarity index 83% rename from src/pages/admin/DeviceStats.jsx rename to src/pages/admin/DeviceStats.tsx index d9a5560..bedc99e 100644 --- a/src/pages/admin/DeviceStats.jsx +++ b/src/pages/admin/DeviceStats.tsx @@ -1,41 +1,52 @@ import { Button, DatePicker, Input, Space, Spin, Table, message } from "antd"; -import dayjs from "dayjs"; -import { useEffect, useState } from "react"; +import dayjs, { Dayjs } from "dayjs"; +import { useCallback, useEffect, useState } from "react"; import axiosInstance from "../../api/axios"; import { datePresets } from "../../config/datePresetsConfig"; import DeviceDetailStatsModal from "./DeviceDetailStatsModal"; +interface UsageStats { + deviceId: string; + deviceName: string; + usageCount: number; + totalUsageDays: number; +} + const { RangePicker } = DatePicker; export default function DeviceStats() { - const [data, setData] = useState([]); - const [filteredData, setFilteredData] = useState([]); - const [range, setRange] = useState([ + const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); + const [range, setRange] = useState<[Dayjs, Dayjs]>([ dayjs().startOf("month"), dayjs().endOf("month"), ]); + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars const [search, setSearch] = useState(""); const [loading, setLoading] = useState(false); const [visiable, setVisiable] = useState(false); const [selectedRecord, setSelectedRecord] = useState(null); - const fetchData = async () => { + const fetchData = useCallback(async () => { setLoading(true); try { - const res = await axiosInstance.get("/device/usage-stats", { - params: { - start: range[0].format("YYYY-MM-DD"), - end: range[1].format("YYYY-MM-DD"), - }, - }); + const res = await axiosInstance.get( + "/device/usage-stats", + { + params: { + start: range[0].format("YYYY-MM-DD"), + end: range[1].format("YYYY-MM-DD"), + }, + } + ); setData(res); setFilteredData(res); - } catch (e) { + } catch { message.error("获取数据失败"); } finally { setLoading(false); } - }; + }, [range]); const handleSearch = (value) => { setSearch(value); @@ -89,7 +100,7 @@ export default function DeviceStats() { useEffect(() => { fetchData(); - }, [range]); + }, [fetchData]); const columns = [ { diff --git a/src/pages/admin/ReservationStats.jsx b/src/pages/admin/ReservationStats.tsx similarity index 80% rename from src/pages/admin/ReservationStats.jsx rename to src/pages/admin/ReservationStats.tsx index a83a5f3..8a43f1e 100644 --- a/src/pages/admin/ReservationStats.jsx +++ b/src/pages/admin/ReservationStats.tsx @@ -1,38 +1,50 @@ import { Button, DatePicker, Input, Space, Spin, Table, message } from "antd"; -import dayjs from "dayjs"; -import { useEffect, useState } from "react"; +import dayjs, { Dayjs } from "dayjs"; +import { useCallback, useEffect, useState } from "react"; import axiosInstance from "../../api/axios"; import { datePresets } from "../../config/datePresetsConfig"; +interface ReservationStat { + deviceId: string; + deviceName: string; + applicantName: string; + applicantTeam: string; + usageCount: number; +} + const { RangePicker } = DatePicker; export default function ReservationStats() { - const [data, setData] = useState([]); - const [filteredData, setFilteredData] = useState([]); - const [range, setRange] = useState([ + const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); + const [range, setRange] = useState<[Dayjs, Dayjs]>([ dayjs().startOf("month"), dayjs().endOf("month"), ]); + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars const [search, setSearch] = useState(""); const [loading, setLoading] = useState(false); - const fetchData = async () => { + const fetchData = useCallback(async () => { setLoading(true); try { - const res = await axiosInstance.get("/reservation/stats", { - params: { - start: range[0].format("YYYY-MM-DD"), - end: range[1].format("YYYY-MM-DD"), - }, - }); + const res = await axiosInstance.get( + "/reservation/stats", + { + params: { + start: range[0].format("YYYY-MM-DD"), + end: range[1].format("YYYY-MM-DD"), + }, + } + ); setData(res); setFilteredData(res); - } catch (e) { + } catch { message.error("获取数据失败"); } finally { setLoading(false); } - }; + }, [range]); const handleSearch = (value) => { setSearch(value); @@ -86,7 +98,7 @@ export default function ReservationStats() { useEffect(() => { fetchData(); - }, [range]); + }, [fetchData]); const columns = [ { diff --git a/src/pages/admin/TeamDetailModal.jsx b/src/pages/admin/TeamDetailModal.tsx similarity index 58% rename from src/pages/admin/TeamDetailModal.jsx rename to src/pages/admin/TeamDetailModal.tsx index 905a30a..85b3a53 100644 --- a/src/pages/admin/TeamDetailModal.jsx +++ b/src/pages/admin/TeamDetailModal.tsx @@ -1,20 +1,22 @@ -import { List, Modal, Typography } from "antd"; -import { useEffect, useState } from "react"; +import { List, Modal } from "antd"; +import { useCallback, useEffect, useState } from "react"; import axiosInstance from "../../api/axios"; export default function TeamDetailModal({ open, team, onclose }) { - const [data, setData] = useState([]); + const [data, setData] = useState([]); - const fetchData = async () => { - const data = await axiosInstance.get(`/user-team/${team.id}`); + const fetchData = useCallback(async () => { + const data = await axiosInstance.get( + `/user-team/${team.id}` + ); setData(data); - }; + }, [team]); useEffect(() => { if (open) { fetchData(); } - }, [open, team]); + }, [open, fetchData]); return ( ([]); + const [data, setData] = useState([]); + const [searchName, setSearchName] = useState(); const [editingId, setEditingId] = useState(); const [editingName, setEditingName] = useState(""); - const [newTeamName, setNewTeamName] = useState(); + const [newTeamName, setNewTeamName] = useState(""); const [open, setOpen] = useState(false); const [selectedTeam, setSelectedTeam] = useState(null); const fetchData = async () => { - const data = await axiosInstance.get("/teams"); + const data = await axiosInstance.get("/teams"); setData(data); setTeams(data); setSearchName(null); diff --git a/src/pages/admin/UserDetailModal.jsx b/src/pages/admin/UserDetailModal.tsx similarity index 86% rename from src/pages/admin/UserDetailModal.jsx rename to src/pages/admin/UserDetailModal.tsx index d34a7e9..b66d4f6 100644 --- a/src/pages/admin/UserDetailModal.jsx +++ b/src/pages/admin/UserDetailModal.tsx @@ -1,7 +1,15 @@ import { Form, Input, message, Modal, Select } from "antd"; import { useEffect, useState } from "react"; import axiosInstance from "../../api/axios"; -import Password from "antd/es/input/Password"; + +interface UserDTO { + username: string; + name: string; + phone: string; + password?: string; + teamId: string; + roleId: string; +} export default function UserDetailModal({ visiable, @@ -12,11 +20,14 @@ export default function UserDetailModal({ onSuccess, }) { const [form] = Form.useForm(); - const [initialValues, setInitialValues] = useState(); - const [teams, setTeams] = useState([]); + const [initialValues, setInitialValues] = useState(); + const [teams, setTeams] = useState<{ label: string; value: string }[]>(); const fetchTeams = async () => { - const data = await axiosInstance.get("/team-label"); + const data = await axiosInstance.get< + unknown, + { label: string; value: string }[] + >("/team-label"); setTeams(data); }; @@ -27,7 +38,7 @@ export default function UserDetailModal({ useEffect(() => { if (visiable) { if (mode === "edit") { - const values = { + const values: UserDTO = { username: user.username, name: user.name, phone: user.phone, @@ -38,7 +49,7 @@ export default function UserDetailModal({ setInitialValues(values); form.setFieldsValue(values); } else { - const values = { + const values: UserDTO = { username: undefined, password: undefined, name: undefined, diff --git a/src/pages/admin/UserManage.jsx b/src/pages/admin/UserManage.tsx similarity index 83% rename from src/pages/admin/UserManage.jsx rename to src/pages/admin/UserManage.tsx index f5cfad6..88cdfc8 100644 --- a/src/pages/admin/UserManage.jsx +++ b/src/pages/admin/UserManage.tsx @@ -2,29 +2,33 @@ import { Button, Flex, Input, Popconfirm, Space, Table } from "antd"; import Column from "antd/es/table/Column"; import { useEffect, useState } from "react"; import axiosInstance from "../../api/axios"; -import DeviceDetailModal from "../deviceAdmin/DeviceDetailModal"; import UserDetailModal from "./UserDetailModal"; +import { PageResult, Pagination } from "types/common"; +import { UserVo } from "types/model"; export default function UserManage() { const [users, setUsers] = useState([]); - const [teams, setTeams] = useState([]); - const [modalMode, setModalMode] = useState(); - const [selectedUser, setSelectedUser] = useState(); + // const [teams, setTeams] = useState([]); + const [modalMode, setModalMode] = useState(); + const [selectedUser, setSelectedUser] = useState(); const [modalOpen, setModalOpen] = useState(false); - const [roles, setRoles] = useState([]); - const [pagination, setPagination] = useState({ + const [roles, setRoles] = useState<{ label: string; value: string }[]>([]); + const [pagination, setPagination] = useState({ current: 1, pageSize: 10, total: 0, }); const fetchRoles = async () => { - const data = await axiosInstance.get("/role"); + const data = await axiosInstance.get< + unknown, + { label: string; value: string }[] + >("/role"); setRoles(data); }; - const fetchData = async (pagination, name) => { - const data = await axiosInstance.get("/user", { + const fetchData = async (pagination: Pagination, name?: string) => { + const data = await axiosInstance.get>("/user", { params: { page: pagination.current, size: pagination.pageSize, @@ -108,7 +112,7 @@ export default function UserManage() { /> { + render={(_, record: UserVo) => { return (