提交 9b39dd3e authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): migrate access admin module

上级 c36c6000
# access/admin migration report
## Status
Migrated `/jurisdiction/admin` from `pages/jurisdiction/admin` to the module-owned
`modules/access/admin` boundary. The active access route now lazy-loads the new
module route and view, and the replaced legacy page/request files were removed
after a successful pre-delete production build.
## Preserved contract
- List API remains `POST /user/user/getList` through `utils/request.js` with a
`qs.stringify` body containing `page`, `page_size`, and active filters.
- Role options remain `POST /user/role/getAllList` with `{ type: 1 }`.
- Add, detail, edit, delete, status, and password endpoints are unchanged.
- Existing success/error messages and false-return behavior are retained in the
module API adapter.
- Filters remain administrator name, phone, role, and validity range. Date
values are sent as local-day `start_time` / `end_time` second timestamps.
- Pagination remains 1/10 initially with 10/20/30/40 page-size choices.
- All eight columns are retained in order, including the status switch and the
edit/password/delete operation column.
- Permissions remain exact, including the existing `/jurisdiction/role/add`
permission for the administrator Add button.
- Add/edit, password, and delete flows remain module-private components with the
original field validation and confirmation copy.
- Successful mutations refresh the current page without filters, matching the
legacy direct `init()` calls; failed mutations do not refresh.
## Architecture
- `api.ts` owns the legacy request protocol and awaits responses before reading
the response body.
- `query.ts` owns filter-to-request mapping.
- `useAdminList.ts` owns controlled list/filter/pagination/loading/error state,
retry, and stale-response protection.
- `views/AdminListView.tsx` owns permissions, operations, role loading, and
AppList composition.
- `components/` owns the add/edit drawer, password drawer, and delete modal.
- `routes.tsx` owns the leaf route without changing the public URL.
## Verification
- Focused oxlint and oxfmt checks pass for `modules/access/admin`.
- `pnpm test` passed all 11 global Node tests.
- Post-delete `pnpm build` passed with only the existing formula-editor and
large-chunk warnings.
- `pnpm exec tsc --noEmit` reports no access/admin diagnostics; unrelated
existing/concurrent diagnostics remain elsewhere in the workspace.
## Concerns
- No API/query/Hook tests were added, per the updated task instruction.
- Deprecated `src/routes/routes.jsx` and `src/config/component.js` still contain
the legacy page path as inactive metadata. They are outside the authorized
scope; the active `src/app/routes.tsx -> modules/access/routes.tsx` chain and
production build use the new module.
import { message } from "antd";
// @ts-expect-error The legacy workspace does not install the qs declaration package.
import qs from "qs";
import request from "@/utils/request.js";
import type { AdminQuery } from "./query";
export interface AdminRow {
id: number;
real_name?: string;
tel?: string;
role_id?: number;
role_name?: string;
status?: number | string;
validity?: string;
create_time?: string;
}
export interface RoleOption {
id: number;
name: string;
type?: number;
}
export interface AdminListResult {
rows: AdminRow[];
total: number;
}
type ApiParams = Record<string, unknown>;
async function getPayload<T>(url: string, params: ApiParams): Promise<T | false> {
try {
const response = await request.post(url, qs.stringify(params));
const body = response.data as { code?: number; data?: T; message?: string };
const code = body?.code ?? 0;
const data = (body?.data ?? []) as T;
const text = body?.message ?? "数据获取失败!";
if (code === 200) return data;
message.error(text);
return false;
} catch {
return false;
}
}
async function mutate(url: string, params: ApiParams): Promise<boolean> {
try {
const response = await request.post(url, qs.stringify(params));
const body = response.data as { code?: number; message?: string };
const code = body?.code ?? 0;
const text = body?.message ?? "数据获取失败!";
if (code === 200) {
message.success(text);
return true;
}
message.error(text);
return false;
} catch {
return false;
}
}
export async function getAdminList(params: AdminQuery): Promise<AdminListResult | false> {
const data = await getPayload<{ list?: AdminRow[]; total?: number }>("/user/user/getList", {
...params,
});
if (data === false) return false;
return { rows: data.list ?? [], total: data.total ?? 0 };
}
export function getRoleOptions() {
return getPayload<RoleOption[]>("/user/role/getAllList", { type: 1 });
}
export function getAdminInfo(id: number) {
return getPayload<AdminRow>("/user/user/getInfoById", { id });
}
export function addAdmin(params: ApiParams) {
return mutate("/user/user/add", params);
}
export function editAdmin(params: ApiParams) {
return mutate("/user/user/edit", params);
}
export function deleteAdmin(id: number) {
return mutate("/user/user/del", { id });
}
export function changeAdminStatus(id: number, status: "0" | "1") {
return mutate("/user/user/changeStatus", { status, id });
}
export function changeAdminPassword(params: ApiParams) {
return mutate("/user/user/editPass", params);
}
import { Button, DatePicker, Drawer, Form, Input, Select, Space } from "antd";
import type { Dayjs } from "dayjs";
import { useEffect } from "react";
import type { RoleOption } from "../api";
export interface AdminFormValues {
real_name: string;
tel: string;
role_id: number;
validity: Dayjs;
password?: string;
}
interface AdminFormDrawerProps {
open: boolean;
isAdd: boolean;
initialAdmin: Partial<AdminFormValues> | null;
roles: RoleOption[];
onClose: () => void;
onSubmit: (values: AdminFormValues) => void | Promise<void>;
}
const passwordPattern =
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/;
export default function AdminFormDrawer({
open,
isAdd,
initialAdmin,
roles,
onClose,
onSubmit,
}: AdminFormDrawerProps) {
const [form] = Form.useForm<AdminFormValues>();
useEffect(() => {
if (!open) {
form.resetFields();
return;
}
if (initialAdmin) {
form.setFieldsValue(initialAdmin as unknown as AdminFormValues);
}
}, [form, initialAdmin, open]);
return (
<Drawer placement="right" onClose={onClose} open={open} mask={false}>
<Form form={form} onFinish={onSubmit} labelCol={{ span: 6 }}>
<Form.Item
name="real_name"
label="管理员名称"
rules={[
{ required: true, message: "请输入管理员名称" },
{ max: 30, message: "最大30字符" },
]}
>
<Input autoComplete="off" placeholder="请输入管理员名称" />
</Form.Item>
<Form.Item
name="tel"
label="手机号"
rules={[
{ required: true, message: "请输入手机号" },
{
pattern: /^(?:(?:\+|00)86)?1[3-9]\d{9}$/,
message: "请输入正确格式的手机号",
},
]}
>
<Input autoComplete="off" placeholder="请输入手机号" />
</Form.Item>
<Form.Item name="role_id" label="角色" rules={[{ required: true, message: "请选择角色" }]}>
<Select placeholder="请选择角色" showSearch optionFilterProp="children">
{roles.map((role) => (
<Select.Option key={role.id} value={role.id}>
{role.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="validity"
label="有效期"
rules={[{ required: true, message: "请选择有效期" }]}
>
<DatePicker placeholder="请选择有效期" />
</Form.Item>
{isAdd ? (
<Form.Item
name="password"
label="初始密码"
rules={[
{ required: true, message: "请输入密码" },
{
pattern: passwordPattern,
message:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!",
},
]}
>
<Input allowClear placeholder="请输入密码" autoComplete="off" />
</Form.Item>
) : null}
<Form.Item wrapperCol={{ offset: 5, span: 16 }} style={{ textAlign: "center" }}>
<Space size={20}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" htmlType="submit">
提交
</Button>
</Space>
</Form.Item>
</Form>
</Drawer>
);
}
import { Modal, Row, Space } from "antd";
interface DeleteAdminModalProps {
open: boolean;
onCancel: () => void;
onConfirm: () => void | Promise<void>;
}
export default function DeleteAdminModal({ open, onCancel, onConfirm }: DeleteAdminModalProps) {
return (
<Modal
mask={false}
centered
open={open}
onOk={onConfirm}
onCancel={onCancel}
footer={(_, { OkBtn, CancelBtn }) => (
<Row justify="center" className="delModal" style={{ marginBottom: "30px" }}>
<Space size={20}>
<CancelBtn />
<OkBtn />
</Space>
</Row>
)}
okText="确认"
>
<p style={{ textAlign: "center", padding: "30px 0 18px", fontSize: 16 }}>
管理员将永久删除,是否继续?
</p>
</Modal>
);
}
import { Button, Drawer, Form, Input, Space } from "antd";
import { useEffect } from "react";
export interface PasswordFormValues {
password: string;
re_password: string;
}
interface PasswordDrawerProps {
open: boolean;
onClose: () => void;
onSubmit: (values: PasswordFormValues) => void | Promise<void>;
}
const passwordPattern =
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/;
const passwordMessage =
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!";
export default function PasswordDrawer({ open, onClose, onSubmit }: PasswordDrawerProps) {
const [form] = Form.useForm<PasswordFormValues>();
useEffect(() => {
if (!open) form.resetFields();
}, [form, open]);
return (
<Drawer mask={false} open={open} onClose={onClose} footer={null}>
<Form labelCol={{ span: 6 }} form={form} wrapperCol={{ span: 12 }} onFinish={onSubmit}>
<Form.Item
label="新密码"
name="password"
wrapperCol={{ span: 16 }}
rules={[
{ required: true, message: "请输入新密码" },
{ pattern: passwordPattern, message: passwordMessage },
{
validator(_, value) {
if (!value || form.getFieldValue("re_password") === value) {
form.setFields([{ name: "re_password", errors: [""] }]);
return Promise.resolve();
}
form.setFields([{ name: "re_password", errors: ["两次输入的密码不一致"] }]);
return Promise.reject();
},
},
]}
>
<Input.Password autoComplete="off" placeholder="请输入新密码" />
</Form.Item>
<Form.Item
label="确认新密码"
name="re_password"
wrapperCol={{ span: 16 }}
rules={[
{ required: true, message: "请再次输入新密码" },
{ pattern: passwordPattern, message: passwordMessage },
{
validator(_, value) {
if (!value || form.getFieldValue("password") === value) return Promise.resolve();
return Promise.reject(new Error("两次输入的密码不一致"));
},
},
]}
>
<Input.Password autoComplete="off" placeholder="请再次输入新密码" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 10, span: 24 }}>
<Space size={20}>
<Button onClick={onClose}>取消</Button>
<Button type="primary" htmlType="submit">
提交
</Button>
</Space>
</Form.Item>
</Form>
</Drawer>
);
}
import type { Dayjs } from "dayjs";
export interface AdminFilters {
real_name?: string;
tel?: string;
role_id?: string | number | null;
time?: [Dayjs | null, Dayjs | null] | null;
}
export interface AdminQuery {
page: number;
page_size: number;
real_name?: string;
tel?: string;
role_id?: string | number;
start_time?: number;
end_time?: number;
}
interface BuildAdminQueryInput {
page: number;
pageSize: number;
filters: AdminFilters;
}
export function buildAdminQuery({ page, pageSize, filters }: BuildAdminQueryInput): AdminQuery {
const query: AdminQuery = { page, page_size: pageSize };
if (filters.real_name) query.real_name = filters.real_name;
if (filters.tel) query.tel = filters.tel;
if (filters.role_id) query.role_id = filters.role_id;
if (filters.time?.[0] && filters.time[1]) {
query.start_time = filters.time[0].startOf("day").unix();
query.end_time = filters.time[1].endOf("day").unix();
}
return query;
}
import { Navigate, useRoutes } from "react-router";
import AdminListView from "./views/AdminListView";
export default function AdminRoutes() {
return useRoutes([
{ index: true, Component: AdminListView },
{ path: "*", element: <Navigate to="/jurisdiction/admin" /> },
]);
}
import { useCallback, useEffect, useRef, useState } from "react";
import { getAdminList, type AdminListResult, type AdminRow } from "./api";
import { buildAdminQuery, type AdminFilters, type AdminQuery } from "./query";
const INITIAL_PAGE = 1;
const INITIAL_PAGE_SIZE = 10;
function isCleared(previous: AdminFilters, next: AdminFilters) {
const keys = new Set([...Object.keys(previous), ...Object.keys(next)]) as Set<keyof AdminFilters>;
return [...keys].some((key) => previous[key] !== next[key] && !next[key]);
}
export function useAdminList() {
const [filters, setFilterState] = useState<AdminFilters>({});
const [appliedFilters, setAppliedFilters] = useState<AdminFilters>({});
const [page, setPage] = useState(INITIAL_PAGE);
const [pageSize, setPageSize] = useState(INITIAL_PAGE_SIZE);
const [rows, setRows] = useState<AdminRow[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const requestId = useRef(0);
const lastQuery = useRef<AdminQuery>(
buildAdminQuery({ page: INITIAL_PAGE, pageSize: INITIAL_PAGE_SIZE, filters: {} }),
);
const load = useCallback(async (query: AdminQuery) => {
const currentRequest = ++requestId.current;
lastQuery.current = query;
setLoading(true);
setError(null);
try {
const result: AdminListResult | false = await getAdminList(query);
if (currentRequest !== requestId.current) return;
if (result === false) {
setError(new Error("数据获取失败!"));
return;
}
setRows(result.rows);
setTotal(result.total);
} catch (reason) {
if (currentRequest === requestId.current) {
setError(reason instanceof Error ? reason : new Error("数据获取失败!"));
}
} finally {
if (currentRequest === requestId.current) setLoading(false);
}
}, []);
const loadFor = useCallback(
(nextPage: number, nextPageSize: number, nextFilters: AdminFilters) =>
load(buildAdminQuery({ page: nextPage, pageSize: nextPageSize, filters: nextFilters })),
[load],
);
useEffect(() => {
void load(lastQuery.current);
return () => {
requestId.current += 1;
};
}, [load]);
const setFilters = useCallback(
(nextFilters: AdminFilters) => {
const shouldReload = isCleared(filters, nextFilters);
setFilterState(nextFilters);
if (shouldReload) {
setAppliedFilters(nextFilters);
void loadFor(page, pageSize, nextFilters);
}
},
[filters, loadFor, page, pageSize],
);
const search = useCallback(() => {
setAppliedFilters(filters);
setPage(INITIAL_PAGE);
void loadFor(INITIAL_PAGE, pageSize, filters);
}, [filters, loadFor, pageSize]);
const reset = useCallback(() => {
const emptyFilters: AdminFilters = {};
setFilterState(emptyFilters);
setAppliedFilters(emptyFilters);
setPage(INITIAL_PAGE);
void loadFor(INITIAL_PAGE, pageSize, emptyFilters);
}, [loadFor, pageSize]);
const refresh = useCallback(() => {
setAppliedFilters(filters);
void loadFor(page, pageSize, filters);
}, [filters, loadFor, page, pageSize]);
const refreshUnfiltered = useCallback(() => {
void loadFor(page, pageSize, {});
}, [loadFor, page, pageSize]);
const changePage = useCallback(
(nextPage: number) => {
setPage(nextPage);
void loadFor(nextPage, pageSize, appliedFilters);
},
[appliedFilters, loadFor, pageSize],
);
const changePageSize = useCallback(
(nextPageSize: number) => {
setPage(INITIAL_PAGE);
setPageSize(nextPageSize);
void loadFor(INITIAL_PAGE, nextPageSize, appliedFilters);
},
[appliedFilters, loadFor],
);
const retry = useCallback(() => void load(lastQuery.current), [load]);
return {
filters,
setFilters,
page,
pageSize,
rows,
total,
loading,
error,
search,
reset,
refresh,
refreshUnfiltered,
changePage,
changePageSize,
retry,
};
}
import { Button, DatePicker, Input, Select, Space, Switch } from "antd";
import type { ColumnsType } from "antd/es/table";
import dayjs from "dayjs";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { AppList, type AppListFilterItem } from "@/shared/components/AppList";
import {
addAdmin,
changeAdminPassword,
changeAdminStatus,
deleteAdmin,
editAdmin,
getAdminInfo,
getRoleOptions,
type AdminRow,
type RoleOption,
} from "../api";
import AdminFormDrawer, { type AdminFormValues } from "../components/AdminFormDrawer";
import DeleteAdminModal from "../components/DeleteAdminModal";
import PasswordDrawer, { type PasswordFormValues } from "../components/PasswordDrawer";
import type { AdminFilters } from "../query";
import { useAdminList } from "../useAdminList";
interface RootState {
user: {
operationPermissionsList?: string[];
};
}
const { RangePicker } = DatePicker;
export default function AdminListView() {
const list = useAdminList();
const permissions = useSelector((state: RootState) => state.user.operationPermissionsList ?? []);
const [roles, setRoles] = useState<RoleOption[]>([]);
const [selectedId, setSelectedId] = useState<number | null>(null);
const [formOpen, setFormOpen] = useState(false);
const [isAdd, setIsAdd] = useState(false);
const [initialAdmin, setInitialAdmin] = useState<Partial<AdminFormValues> | null>(null);
const [passwordOpen, setPasswordOpen] = useState(false);
const [deleteOpen, setDeleteOpen] = useState(false);
useEffect(() => {
let active = true;
void getRoleOptions().then((result) => {
if (active && result !== false) setRoles(result);
});
return () => {
active = false;
};
}, []);
const handleEdit = async (id: number) => {
const admin = await getAdminInfo(id);
if (admin === false) return;
setSelectedId(id);
setIsAdd(false);
setInitialAdmin({
real_name: admin.real_name,
tel: admin.tel,
role_id: admin.role_id,
validity: dayjs(admin.validity),
});
setFormOpen(true);
};
const handleFormSubmit = async (values: AdminFormValues) => {
const params = { ...values, validity: dayjs(values.validity).unix() };
const success = isAdd ? await addAdmin(params) : await editAdmin({ ...params, id: selectedId });
if (!success) return;
list.refreshUnfiltered();
setFormOpen(false);
};
const handlePasswordSubmit = async ({ password, re_password }: PasswordFormValues) => {
if (selectedId === null) return;
const success = await changeAdminPassword({
id: selectedId,
password,
re_password,
});
if (!success) return;
list.refreshUnfiltered();
setPasswordOpen(false);
};
const handleDelete = async () => {
if (selectedId === null) return;
const success = await deleteAdmin(selectedId);
if (success) list.refreshUnfiltered();
setDeleteOpen(false);
};
const handleStatus = async (checked: boolean, id: number) => {
const success = await changeAdminStatus(id, checked ? "1" : "0");
if (success) list.refreshUnfiltered();
};
const columns: ColumnsType<AdminRow> = [
{ title: "ID", key: "id", dataIndex: "id", align: "center" },
{
title: "管理员名称",
key: "real_name",
dataIndex: "real_name",
align: "center",
},
{ title: "手机号", key: "tel", dataIndex: "tel", align: "center" },
{
title: "角色名称",
key: "role_name",
dataIndex: "role_name",
align: "center",
},
{
title: "状态",
key: "sort",
dataIndex: "sort",
align: "center",
render: (_, record) => (
<Switch
checked={Number(record.status) === 1}
onChange={(checked) => void handleStatus(checked, record.id)}
disabled={!permissions.includes("/jurisdiction/admin/changeStatus")}
/>
),
},
{ title: "有效期", key: "validity", dataIndex: "validity", align: "center" },
{
title: "创建时间",
key: "create_time",
dataIndex: "create_time",
align: "center",
},
{
title: "操作",
key: "handle",
dataIndex: "handle",
render: (_, record) => (
<Space>
{permissions.includes("/jurisdiction/admin/getInfoById") ? (
<Button onClick={() => void handleEdit(record.id)}>编辑</Button>
) : null}
{permissions.includes("/jurisdiction/admin/editPass") ? (
<Button
onClick={() => {
setSelectedId(record.id);
setPasswordOpen(true);
}}
>
修改密码
</Button>
) : null}
{permissions.includes("/jurisdiction/admin/del") ? (
<Button
onClick={() => {
setSelectedId(record.id);
setDeleteOpen(true);
}}
>
删除
</Button>
) : null}
</Space>
),
},
];
const filterItems: AppListFilterItem<AdminFilters>[] = [
{
name: "real_name",
label: "管理员名称",
render: (field) => (
<Input
autoComplete="off"
allowClear
value={field.value ?? ""}
onChange={(event) => field.onChange(event.target.value)}
placeholder="请输入管理员名称"
id="name"
/>
),
},
{
name: "tel",
label: "手机号",
render: (field) => (
<Input
autoComplete="off"
allowClear
value={field.value ?? ""}
onChange={(event) => field.onChange(event.target.value)}
placeholder="请输入手机号"
id="phone"
/>
),
},
{
name: "role_id",
label: "角色",
render: (field) => (
<Select
placeholder="请选择角色"
allowClear
style={{ width: "200px" }}
value={field.value}
onChange={field.onChange}
options={roles.map((role) => ({ label: role.name, value: role.id }))}
/>
),
},
{
name: "time",
label: "有效期",
render: (field) => (
<RangePicker
value={field.value}
onChange={(value) => field.onChange(value)}
allowClear
id="createTime"
/>
),
},
];
const toolbar = permissions.includes("/jurisdiction/role/add") ? (
<Button
type="primary"
ghost
onClick={() => {
setSelectedId(null);
setInitialAdmin(null);
setIsAdd(true);
setFormOpen(true);
}}
>
添加
</Button>
) : undefined;
return (
<>
<AppList<AdminRow, AdminFilters>
rowKey="id"
columns={columns}
data={{
rows: list.rows,
total: list.total,
loading: list.loading,
error: list.error,
}}
filters={{
value: list.filters,
items: filterItems,
onChange: list.setFilters,
onSearch: list.search,
onReset: list.reset,
onRefresh: list.refresh,
}}
pagination={{
page: list.page,
pageSize: list.pageSize,
pageSizeOptions: [10, 20, 30, 40],
onChange: list.changePage,
onPageSizeChange: list.changePageSize,
}}
toolbar={toolbar}
onRetry={list.retry}
/>
<AdminFormDrawer
open={formOpen}
isAdd={isAdd}
initialAdmin={initialAdmin}
roles={roles}
onClose={() => setFormOpen(false)}
onSubmit={handleFormSubmit}
/>
<PasswordDrawer
open={passwordOpen}
onClose={() => setPasswordOpen(false)}
onSubmit={handlePasswordSubmit}
/>
<DeleteAdminModal
open={deleteOpen}
onCancel={() => setDeleteOpen(false)}
onConfirm={handleDelete}
/>
</>
);
}
......@@ -3,7 +3,7 @@ import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const AccessLayout = lazy(() => import("@/pages/jurisdiction/index"));
const Admin = lazy(() => import("@/pages/jurisdiction/admin/index"));
const AdminRoutes = lazy(() => import("./admin/routes"));
const Role = lazy(() => import("@/pages/jurisdiction/role/index"));
export default function AccessRoutes() {
......@@ -12,7 +12,7 @@ export default function AccessRoutes() {
Component: AccessLayout,
children: [
{ index: true, element: <Navigate to="/jurisdiction/role" /> },
{ path: "admin", Component: Admin },
{ path: "admin/*", Component: AdminRoutes },
{ path: "role", Component: Role },
],
},
......
import React, { useEffect, useState, useRef } from "react";
import {
Table,
Input,
Button,
Row,
Col,
Space,
DatePicker,
Pagination,
Drawer,
Form,
Modal,
Switch,
Select,
Image,
} from "antd";
import { useNavigate } from "react-router";
const { RangePicker } = DatePicker;
import PaginationCom from "@/common/Pagination";
import { getList1, addRole, infoRole, editRole, changeStatus, delRole, editPass } from "./request";
import { getAllList } from "../role/request";
import reload from "@/assets/images/icon/reload.png";
import reset from "@/assets/images/icon/reset.png";
import filter from "@/assets/images/icon/filter.png";
import add from "@/assets/images/icon/add.png";
import dayjs from "dayjs";
import TableCom from "@/common/TableCom/index";
import { useSelector } from "react-redux";
const Administrator = () => {
const [data, setData] = useState([]);
const navigate = useNavigate();
const [page_size, setpage_size] = useState(10);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [showModal, setshowModal] = useState(false);
const [isAdd, setIsAdd] = useState(false);
const [delMoadal, setDelModal] = useState(false);
const [passwordModal, setPasswordModal] = useState(false);
const [form] = Form.useForm();
const [form2] = Form.useForm();
const [id, setId] = useState(0);
const flag = useRef(true);
// 获取操作权限
const { operationPermissionsList } = useSelector((state) => state.user);
// 角色列表
const [roleList, setRoleList] = useState([]);
const [initialValues, setinitialValues] = useState({
real_name: "",
tel: "",
role_id: "",
validity: "",
password: "",
});
const [filterObj, setfilterObj] = useState({
real_name: "",
tel: "",
role_id: null,
start_time: "",
end_time: "",
});
const [dateVal, setDateVal] = useState([]);
// 过滤
const handleFilter = (flag, val) => {
if (flag == "startandend") {
let val2 = val || [];
filterObj["start_time"] = parseInt(
new Date(dayjs(val2[0]).format("YYYY-MM-DD") + " 00:00:00").getTime() / 1000,
);
const endDate = new Date(dayjs(val2[1])).getDate();
const endTime = parseInt(
new Date(dayjs(val2[1]).format("YYYY-MM-") + endDate + " 23:59:59").getTime() / 1000,
);
filterObj["end_time"] = endTime;
setDateVal(val);
if (!val2.length) {
delete filterObj["start_time"];
delete filterObj["end_time"];
}
} else {
filterObj[flag] = val;
}
for (const key in filterObj) {
if (!filterObj[key]) {
delete filterObj[key];
}
}
setfilterObj({ ...filterObj });
if (!val) init(filterObj);
};
// 重置
const handleReset = () => {
setfilterObj({ real_name: "", tel: "", role_id: "" });
setDateVal([]);
init();
};
const init = async (obj = {}) => {
setLoading(true);
const { total, list } = await getList1({ page, page_size, ...obj });
setTotal(total);
setData(list);
setLoading(false);
};
const handleEdit = async (id) => {
console.log("handleEdit id", id);
const data = await infoRole({ id });
let { validity } = data;
setIsAdd(false);
setshowModal(true);
setId(id);
form.setFieldsValue({ ...data, validity: dayjs(validity) });
};
const onClose = () => {
setshowModal(false);
};
const submitForm = async (obj) => {
let bool;
let { validity } = obj;
if (isAdd) {
bool = await addRole({
...obj,
validity: dayjs(validity).unix(),
});
} else {
bool = await editRole({ ...obj, id, validity: dayjs(validity).unix() });
}
if (!bool) return;
init();
setshowModal(false);
};
const delSuccess = async (id) => {
console.log("id", id);
const bool = await delRole({ id });
console.log("bool", bool);
bool && init();
setDelModal(false);
};
const handleStatus = async (status, id) => {
const bool = await changeStatus({ status: status ? "1" : "0", id });
bool && init();
};
const passwordSuccess = async (obj) => {
let { password: pass, re_password: repass } = obj;
const bool = await editPass({ id, password: pass, re_password: repass });
if (!bool) return;
init();
setPasswordModal(false);
};
// 获取角色
const RoleList = async () => {
const res = await getAllList({ type: 1 });
setRoleList(res);
};
useEffect(() => {
// if(!flag.current){
RoleList();
// }else{
// flag.current=false
// }
}, []);
useEffect(() => {
if (!passwordModal) {
form2.setFieldsValue({
password: "",
re_password: "",
});
}
}, [passwordModal]);
const columns = [
{
title: "ID",
key: "id",
dataIndex: "id",
align: "center",
},
{
title: "管理员名称",
key: "real_name",
dataIndex: "real_name",
align: "center",
},
{
title: "手机号",
key: "tel",
dataIndex: "tel",
align: "center",
},
{
title: "角色名称",
key: "role_name",
dataIndex: "role_name",
align: "center",
},
{
title: "状态",
key: "sort",
dataIndex: "sort",
render(_, { status, id }) {
return (
<Switch
defaultChecked={status}
onChange={(ev) => handleStatus(ev, id)}
disabled={!operationPermissionsList.includes("/jurisdiction/admin/changeStatus")}
/>
);
},
align: "center",
},
{
title: "有效期",
key: "validity",
align: "center",
dataIndex: "validity",
},
{
title: "创建时间",
key: "create_time",
align: "center",
dataIndex: "create_time",
},
{
title: "操作",
key: "handle",
dataIndex: "handle",
render: (_, record) => {
return (
<Space>
{operationPermissionsList.includes("/jurisdiction/admin/getInfoById") && (
<Button onClick={() => handleEdit(record.id)}>编辑</Button>
)}
{operationPermissionsList.includes("/jurisdiction/admin/editPass") && (
<Button
onClick={() => {
setId(record.id);
setPasswordModal(true);
}}
>
修改密码
</Button>
)}
{operationPermissionsList.includes("/jurisdiction/admin/del") && (
<Button
onClick={() => {
setId(record.id);
setDelModal(true);
}}
>
删除
</Button>
)}
</Space>
);
},
},
];
useEffect(() => {
if (!showModal) {
form.setFieldsValue({
real_name: "",
tel: "",
role_id: null,
validity: "",
password: "",
});
form.resetFields();
}
}, [showModal]);
return (
<div>
<Row justify={"space-between"}>
<Col className="form-devices-inline">
<Form layout="inline">
<Form.Item label="管理员名称">
<Input
autoComplete="off"
allowClear
value={filterObj.real_name}
onChange={(ev) => handleFilter("real_name", ev.target.value)}
placeholder="请输入管理员名称"
id="name"
></Input>
</Form.Item>
<Form.Item label="手机号">
<Input
autoComplete="off"
allowClear
value={filterObj.tel}
onChange={(ev) => handleFilter("tel", ev.target.value)}
placeholder="请输入手机号"
id="phone"
></Input>
</Form.Item>
<Form.Item label="角色" rules={[{ required: true, message: "请选择角色" }]}>
<Select placeholder="请选择角色" allowClear style={{ width: "200px" }}>
{roleList.length &&
roleList.map((item) => (
<Select.Option key={item.id} value={item.id}>
{item.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label="有效期">
<RangePicker
value={dateVal}
onChange={(ev) => handleFilter("startandend", ev)}
allowClear
id="createTime"
></RangePicker>
</Form.Item>
<Space>
<Button
type="primary"
ghost
icon={
<span
style={{
display: "inline-block",
width: "11px",
height: "10px",
pointerEvents: "none",
}}
>
<Image src={reset} />
</span>
}
onClick={handleReset}
>
重置
</Button>
<Button
type="primary"
icon={
<span
style={{
display: "inline-block",
width: "13px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={filter} />
</span>
}
ghost
onClick={() => {
setPage(1);
Object.values(filterObj).some((item) => item) ? init(filterObj) : init();
}}
>
筛选
</Button>
<Button
type="primary"
ghost
icon={
<span
style={{
display: "inline-block",
width: "10px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={reload} />
</span>
}
onClick={() =>
Object.values(filterObj).some((item) => item) ? init(filterObj) : init()
}
>
刷新
</Button>
{/* <Button
type='primary'
ghost
icon={
<span
style={{
display: 'inline-block',
width: '13px',
height: '12px',
pointerEvents: 'none',
}}
>
<Image src={add} />
</span>
}
onClick={() => {
setIsAdd(true);
setshowModal(true);
}}
>
添加
</Button> */}
</Space>
</Form>
</Col>
<Col>
<Space>
{operationPermissionsList.includes("/jurisdiction/role/add") && (
<Button
type="primary"
ghost
icon={
<span
style={{
display: "inline-block",
width: "13px",
height: "12px",
pointerEvents: "none",
}}
>
<Image src={add} />
</span>
}
onClick={() => {
setIsAdd(true);
setshowModal(true);
}}
>
添加
</Button>
)}
</Space>
</Col>
</Row>
<br />
{TableCom({ columns, loading, data })}
<Modal
mask={false}
centered
open={delMoadal}
onOk={() => delSuccess(id)}
onCancel={() => setDelModal(false)}
footer={(_, { OkBtn, CancelBtn }) => {
return (
<Row justify={"center"} className="delModal" style={{ marginBottom: "30px" }}>
<Space size={20}>
<CancelBtn />
<OkBtn />
</Space>
</Row>
);
}}
okText="确认"
>
<p style={{ textAlign: "center", padding: "30px 0 18px", fontSize: 16 }}>
管理员将永久删除,是否继续?
</p>
</Modal>
<Drawer
mask={false}
centered
open={passwordModal}
onCancel={() => setPasswordModal(false)}
onClose={() => setPasswordModal(false)}
footer={null}
okText="提交"
>
<Form
labelCol={{ span: 6 }}
form={form2}
wrapperCol={{ span: 12 }}
onFinish={passwordSuccess}
>
<Form.Item
label="新密码"
name="password"
wrapperCol={{ span: 16 }}
rules={[
{ required: true, message: "请输入新密码" },
{
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!",
trigger: ["change", "blur"],
},
{
validator(_, value) {
if (!value || form2.getFieldValue("re_password") === value) {
form2.setFields([{ name: "re_password", errors: [""] }]);
return Promise.resolve();
}
form2.setFields([{ name: "re_password", errors: ["两次输入的密码不一致"] }]);
return Promise.reject();
},
},
]}
// validateStatus='error' // 设置为 'error' 显示错误样式
>
<Input.Password autoComplete="off" placeholder="请输入新密码"></Input.Password>
</Form.Item>
<Form.Item
label="确认新密码"
name="re_password"
wrapperCol={{ span: 16 }}
rules={[
{ required: true, message: "请再次输入新密码" },
{
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!",
trigger: ["change", "blur"],
},
{
validator(_, value) {
if (!value || form2.getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(new Error("两次输入的密码不一致"));
},
},
]}
>
<Input.Password autoComplete="off" placeholder="请再次输入新密码"></Input.Password>
</Form.Item>
<Form.Item wrapperCol={{ offset: 10, span: 24 }}>
<Space size={20}>
<Button onClick={() => setPasswordModal(false)}>取消</Button>
<Button type="primary" htmlType="submit">
提交
</Button>
</Space>
</Form.Item>
</Form>
</Drawer>
<br />
<PaginationCom
total={total}
page_size={page_size}
setpage_size={setpage_size}
page={page}
setPage={setPage}
init={init}
filterObj={filterObj}
/>
<Drawer placement="right" onClose={onClose} open={showModal} mask={false}>
<Form form={form} onFinish={submitForm} labelCol={{ span: 6 }}>
<Form.Item
name="real_name"
label="管理员名称"
rules={[
{ required: true, message: "请输入管理员名称" },
{ max: 30, message: "最大30字符" },
]}
>
<Input autoComplete="off" placeholder="请输入管理员名称"></Input>
</Form.Item>
<Form.Item
name="tel"
label="手机号"
rules={[
{ required: true, message: "请输入手机号" },
{
pattern: /^(?:(?:\+|00)86)?1[3-9]\d{9}$/,
message: "请输入正确格式的手机号",
trigger: "blur",
},
]}
>
<Input autoComplete="off" placeholder="请输入手机号"></Input>
</Form.Item>
<Form.Item
name="role_id"
label="角色"
rules={[{ required: true, message: "请选择角色" }]}
>
{/* <Select placeholder='请选择角色'>
{roleList
.reduce((acc, item) => {
if (item.type === 1 && !acc.some((el) => el.type === 1)) {
acc.push(item);
}
if (item.type === 2 && !acc.some((el) => el.type === 2)) {
acc.push(item);
}
return acc;
}, [])
.map((item) => (
<Select.Option key={item.id} value={item.id}>
{item.type === 1 ? '管理员' : '老师'}
</Select.Option>
))}
</Select> */}
<Select
placeholder="请选择角色"
showSearch
optionFilterProp="children"
autoComplete="off"
>
{roleList.length &&
roleList.map((item) => (
<Select.Option key={item.id} value={item.id}>
{item.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name="validity"
label="有效期"
rules={[{ required: true, message: "请选择有效期" }]}
>
<DatePicker placeholder="请选择有效期"></DatePicker>
</Form.Item>
{isAdd && (
<Form.Item
name="password"
label="初始密码"
rules={[
{ required: true, message: "请输入密码" },
{
pattern:
/^((?=.*\d)(?=.*[A-Z])(?=.*[a-z]))|((?=.*\d)(?=.*[A-Z])(?=.*[\W_]))|((?=.*\d)(?=.*[a-z])(?=.*[\W_]))|((?=.*[A-Z])(?=.*[a-z])(?=.*[\W_]))[a-zA-Z\d\W_]{8,12}$/,
message:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!",
trigger: ["change", "blur"],
},
]}
>
<Input allowClear placeholder="请输入密码" autoComplete="off" />
{/* <Input.Password
allowClear
placeholder='请输入密码'
autoComplete='new-password'
autoFill='off'
data-autocomplete='off'
style={{ MozAppearance: 'textfield' }}
name={Math.random().toString(36).substring(2)} // 假设这个值是每次都会重新生成的随机字符串
inputProps={{
autocomplete: 'new-password',
style: { WebkitTextSecurity: 'disc'}
, }} /> */}
</Form.Item>
)}
<Form.Item wrapperCol={{ offset: 5, span: 16 }} style={{ textAlign: "center" }}>
<Space size={20}>
<Button onClick={() => setshowModal(false)}>取消</Button>
<Button type="primary" htmlType="submit">
提交
</Button>
</Space>
</Form.Item>
</Form>
</Drawer>
</div>
);
};
export default Administrator;
import axios from "@/utils/request.js";
import qs from "qs";
import { get } from "lodash-es";
import { message } from "antd";
// 管理员列表
const getList1 = async (obj) => {
try {
const res = await axios.post("/user/user/getList", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 添加
const addRole = async (obj) => {
try {
const res = await axios.post("/user/user/add", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 详情
const infoRole = async (obj) => {
try {
const res = await axios.post("/user/user/getInfoById", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
return data;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 修改
const editRole = async (obj) => {
try {
const res = await axios.post("/user/user/edit", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 删除
const delRole = async (obj) => {
try {
const res = await axios.post("/user/user/del", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
const changeStatus = async (obj) => {
try {
const res = await axios.post("/user/user/changeStatus", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
// 修改密码
const editPass = async (obj) => {
try {
const res = await axios.post("/user/user/editPass", qs.stringify(obj));
const code = get(res, "data.code", 0);
const data = get(res, "data.data", []);
const msg = get(res, "data.message", "数据获取失败!");
if (code === 200) {
message.success(msg);
return true;
} else {
message.error(msg);
return false;
}
} catch (error) {
return false;
}
};
export { getList1, addRole, infoRole, editRole, changeStatus, delRole, editPass };
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论