提交 08581ef2 authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): migrate teachers list module

上级 bcb25df1
# Teachers list migration
## Delivered
- Moved the teacher-list route to `modules/teachers/list`, retaining the existing filters, columns, permissions, drawers, modal flows, validation, role lookup, status/password/delete actions, and commission settings.
- Extracted form-encoded API calls, query construction, and list state into `api.ts`, `query.ts`, and `useTeacherList.ts`.
- Replaced only the legacy `TableCom` and `PaginationCom` list frame with controlled `AppList` pagination.
- Deleted the old `pages/teacher/query` implementation after the live module route stopped importing it.
## Verification
- `pnpm build`: passed for both learning and admin.
- `pnpm test`: admin 11/11 passed. The workspace command still fails in pre-existing learning tests, including `src/api/http-auth.test.ts`, `src/modules/my/routes.test.tsx`, and router tests missing a `QueryClientProvider`.
- `git diff --check`: passed.
## Compatibility note
The obsolete `src/routes/routes.jsx` still contains a dead legacy teacher-page declaration, but the live `app/routes.tsx` does not import that file. It is outside this migration's permitted route scope.
import { message } from 'antd'
import { get } from 'lodash-es'
import qs from 'qs'
import axios from '@/utils/request.js'
async function request(
path: string,
payload: Record<string, unknown>,
{ success = false, returnData = false }: { success?: boolean; returnData?: boolean } = {},
) {
try {
const response = await axios.post(path, qs.stringify(payload))
const code = get(response, 'data.code', 0)
const data = get(response, 'data.data', [])
const text = get(response, 'data.message', '数据获取失败!')
if (code !== 200) {
message.error(text)
return false
}
if (success) message.success(text)
return returnData ? data : true
} catch {
return false
}
}
export const getTeacherList = (payload: Record<string, unknown>) =>
request('/user/teacher/getList', payload, { returnData: true })
export const addTeacher = (payload: Record<string, unknown>) =>
request('/user/teacher/add', payload, { success: true })
export const getTeacherInfo = (payload: Record<string, unknown>) =>
request('/user/teacher/getInfoById', payload, { returnData: true })
export const editTeacher = (payload: Record<string, unknown>) =>
request('/user/teacher/edit', payload, { success: true })
export const deleteTeacher = (payload: Record<string, unknown>) =>
request('/user/teacher/del', payload, { success: true })
export const changeTeacherStatus = (payload: Record<string, unknown>) =>
request('/user/teacher/changeStatus', payload, { success: true })
export const changeTeacherPassword = (payload: Record<string, unknown>) =>
request('/user/teacher/editPass', payload, { success: true })
export const getTeacherSystem = (payload: Record<string, unknown>) =>
request('/system/config/getSystem', payload, { returnData: true })
export const updateTeacherSystem = (payload: Record<string, unknown>) =>
request('/system/config/systemEdit', payload, { success: true })
export function buildTeacherQuery({
page,
pageSize,
filters = {},
}: {
page: number
pageSize: number
filters?: Record<string, unknown>
}) {
const query: Record<string, unknown> = { page, page_size: pageSize }
Object.entries(filters).forEach(([key, value]) => {
if (value) query[key] = value
})
return query
}
import { lazy } from 'react'
import { Navigate, useRoutes } from 'react-router'
const TeacherListView = lazy(() => import('./views/TeacherListView'))
export default function TeacherListRoutes() {
return useRoutes([
{ index: true, Component: TeacherListView },
{ path: '*', element: <Navigate to="/teacher/list" /> },
])
}
import { useCallback, useEffect, useRef, useState } from 'react'
import { getTeacherList } from './api'
import { buildTeacherQuery } from './query'
export function useTeacherList() {
const [rows, setRows] = useState<any[]>([])
const [total, setTotal] = useState(0)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
const requestId = useRef(0)
const init = useCallback(
async (
filters: Record<string, unknown> = {},
nextPage = page,
nextPageSize = pageSize,
) => {
const id = ++requestId.current
setLoading(true)
setError(null)
try {
const result = await getTeacherList(
buildTeacherQuery({ page: nextPage, pageSize: nextPageSize, filters }),
)
if (id === requestId.current && result) {
setRows((result as { list?: any[] }).list ?? [])
setTotal((result as { total?: number }).total ?? 0)
}
} catch (reason) {
if (id === requestId.current) {
setError(reason instanceof Error ? reason : new Error('数据获取失败!'))
}
} finally {
if (id === requestId.current) setLoading(false)
}
},
[page, pageSize],
)
useEffect(() => {
void init({}, 1, 10)
}, [])
const changePage = useCallback(
(nextPage: number, filters: Record<string, unknown>) => {
setPage(nextPage)
void init(filters, nextPage, pageSize)
},
[init, pageSize],
)
const changePageSize = useCallback(
(nextPageSize: number, filters: Record<string, unknown>) => {
setPage(1)
setPageSize(nextPageSize)
void init(filters, 1, nextPageSize)
},
[init],
)
return { rows, total, page, pageSize, loading, error, init, changePage, changePageSize }
}
import React, { useEffect, useState } from 'react'
import {
Button,
Col,
DatePicker,
Drawer,
Form,
Image,
Input,
Modal,
Popover,
Row,
Select,
Space,
Switch,
} from 'antd'
import dayjs from 'dayjs'
import { useSelector } from 'react-redux'
import { AppList } from '@/shared/components/AppList'
import { getAllList } from '@/pages/jurisdiction/role/request.js'
import add from '@/assets/images/icon/add.png'
import filter from '@/assets/images/icon/filter.png'
import reload from '@/assets/images/icon/reload.png'
import reset from '@/assets/images/icon/reset.png'
import {
addTeacher,
changeTeacherPassword,
changeTeacherStatus,
deleteTeacher,
editTeacher as updateTeacher,
getTeacherInfo,
getTeacherSystem,
updateTeacherSystem,
} from '../api'
import { useTeacherList } from '../useTeacherList'
const { RangePicker } = DatePicker
const passwordRules = [
{ 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'],
},
]
export default function TeacherListView() {
const { operationPermissionsList } = useSelector((state) => state.user)
const { rows, total, page, pageSize, loading, error, init, changePage, changePageSize } =
useTeacherList()
const [showModal, setShowModal] = useState(false)
const [isAdd, setIsAdd] = useState(false)
const [deleteModal, setDeleteModal] = useState(false)
const [passwordModal, setPasswordModal] = useState(false)
const [isEditValidity, setIsEditValidity] = useState(false)
const [id, setId] = useState(0)
const [roleList, setRoleList] = useState([])
const [dateVal, setDateVal] = useState('')
const [filters, setFilters] = useState({ real_name: '', tel: '', start_time: '', end_time: '' })
const [form] = Form.useForm()
const [passwordForm] = Form.useForm()
const [systemForm] = Form.useForm()
const has = (permission) => operationPermissionsList.includes(permission)
const reloadList = (nextFilters = filters) => init(nextFilters)
useEffect(() => {
getAllList({ type: 2 }).then((result) => result && setRoleList(result))
}, [])
useEffect(() => {
if (!showModal) form.resetFields()
if (!passwordModal) passwordForm.resetFields()
}, [form, passwordForm, showModal, passwordModal])
const getInfo = async () => {
const data = await getTeacherSystem({ type: 1 })
data && systemForm.setFieldsValue({ set_value: data.set_value })
}
useEffect(() => {
if (has('/teacher/list/getSystem')) void getInfo()
}, [operationPermissionsList])
const handleFilter = (name, value) => {
const next = { ...filters }
if (name === 'startandend') {
const range = value || []
if (range.length) {
next.start_time = dayjs(dayjs(range[0]).format('YYYY-MM-DD') + ' 00:00:00').unix()
next.end_time = dayjs(dayjs(range[1]).format('YYYY-MM-DD') + ' 23:59:59').unix()
} else {
delete next.start_time
delete next.end_time
}
setDateVal(value)
} else {
next[name] = value
}
Object.keys(next).forEach((key) => !next[key] && delete next[key])
setFilters(next)
if (!value) void init(next)
}
const resetFilters = () => {
const next = { real_name: '', tel: '' }
setFilters(next)
setDateVal('')
void init(next)
}
const submitTeacher = async (values) => {
const payload = {
...values,
validity: dayjs(dayjs(values.validity).format('YYYY-MM-DD') + ' 23:59:59').unix(),
}
const success = isAdd ? await addTeacher(payload) : await updateTeacher({ ...payload, id })
if (success) {
void reloadList()
setShowModal(false)
}
}
const editTeacher = async (record) => {
const data = await getTeacherInfo({ id: record.id })
if (!data) return
setIsAdd(false)
setId(data.id)
form.setFieldsValue({ ...data, validity: dayjs(data.validity) })
setShowModal(true)
}
const submitPassword = async ({ password, re_password }) => {
const success = await changeTeacherPassword({ id, password, re_password })
if (success) void reloadList()
setPasswordModal(false)
}
const submitDelete = async () => {
const success = await deleteTeacher({ id })
if (success) void reloadList()
setDeleteModal(false)
}
const changeStatus = async (checked, teacherId) => {
const success = await changeTeacherStatus({ id: teacherId, status: checked ? '1' : '0' })
if (success) void reloadList()
}
const submitSystem = async ({ set_value }) => {
const success = await updateTeacherSystem({ type: 1, set_value })
if (success) void getInfo()
}
const columns = [
{ title: 'ID', key: 'id', dataIndex: 'id' },
{
title: '老师名称', key: 'real_name', dataIndex: 'real_name', align: 'center', width: 200,
render: (text) => <Popover content={text} overlayStyle={{ maxWidth: '200px' }}><div style={{ display: '-webkit-box', WebkitLineClamp: '2', WebkitBoxOrient: 'vertical', overflow: 'hidden', textOverflow: 'ellipsis' }}>{text}</div></Popover>,
},
{ title: '手机号', align: 'center', key: 'tel', dataIndex: 'tel' },
{ title: '状态', key: 'status', dataIndex: 'status', render: (_, record) => <Switch checked={Boolean(record.status)} onChange={(checked) => void changeStatus(checked, record.id)} /> },
{ title: '有效期', align: 'center', key: 'validity', dataIndex: 'validity' },
{ title: '创建时间', align: 'center', key: 'create_time', dataIndex: 'create_time' },
{
title: '操作', key: 'handle', align: 'center', dataIndex: 'handle', render: (_, record) => <Space>
{has('/teacher/list/getInfoById') && <Button onClick={() => void editTeacher(record)}>编辑</Button>}
{has('/teacher/list/del') && <Button onClick={() => { setId(record.id); setDeleteModal(true) }}>删除</Button>}
{has('/teacher/list/editPass') && <Button onClick={() => { setId(record.id); setPasswordModal(true) }}>修改密码</Button>}
</Space>,
},
]
const icon = (source, width, height) => <span style={{ display: 'inline-block', width, height, pointerEvents: 'none' }}><Image src={source} /></span>
return <div>
<Row justify="space-between">
<Col className="form-devices-inline">
<Form layout="inline">
<Form.Item label="老师名称"><Input autoComplete="off" allowClear value={filters.real_name} onChange={(event) => handleFilter('real_name', event.target.value)} placeholder="请输入老师名称" id="name" /></Form.Item>
<Form.Item label="手机号"><Input autoComplete="off" allowClear value={filters.tel} onChange={(event) => handleFilter('tel', event.target.value)} placeholder="请输入手机号" id="phone" /></Form.Item>
<Form.Item label="创建时间"><RangePicker value={dateVal} onChange={(value) => handleFilter('startandend', value)} id="createTime" /></Form.Item>
<Space>
<Button type="primary" ghost icon={icon(reset, '11px', '10px')} onClick={resetFilters}>重置</Button>
<Button type="primary" ghost icon={icon(filter, '13px', '12px')} onClick={() => { void init(filters, 1); }}>筛选</Button>
<Button type="primary" ghost icon={icon(reload, '10px', '12px')} onClick={() => { void reloadList(); }}>刷新</Button>
</Space>
</Form>
</Col>
<Col><Space>
{has('/teacher/list/add') && <Button type="primary" ghost icon={icon(add, '13px', '12px')} onClick={() => { setIsAdd(true); setShowModal(true) }}>添加</Button>}
{has('/teacher/list/getSystem') && <Button type="primary" ghost onClick={() => { setIsAdd(true); setIsEditValidity(true) }}>分成设置</Button>}
</Space></Col>
</Row>
<br />
<AppList rowKey="id" columns={columns} data={{ rows, total, loading, error }} pagination={{ page, pageSize, pageSizeOptions: [10, 20, 30, 40], onChange: (nextPage) => changePage(nextPage, filters), onPageSizeChange: (nextPageSize) => changePageSize(nextPageSize, filters) }} onRetry={() => reloadList()} />
<Modal mask={false} centered open={deleteModal} onOk={() => void submitDelete()} onCancel={() => setDeleteModal(false)} 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>
<Drawer mask={false} open={passwordModal} onClose={() => setPasswordModal(false)} footer={null} width={700}>
<Form labelCol={{ span: 6 }} form={passwordForm} wrapperCol={{ span: 12 }} onFinish={submitPassword}>
<Form.Item label="新密码" name="password" wrapperCol={{ span: 16 }} rules={[...passwordRules, { validator: (_, value) => !value || passwordForm.getFieldValue('re_password') === value ? Promise.resolve() : Promise.reject(new Error('两次输入的密码不一致')) }]}><Input.Password autoComplete="off" placeholder="请输入新密码" /></Form.Item>
<Form.Item label="确认新密码" name="re_password" wrapperCol={{ span: 16 }} rules={[...passwordRules, { validator: (_, value) => !value || passwordForm.getFieldValue('password') === value ? Promise.resolve() : Promise.reject(new Error('两次输入的密码不一致')) }]}><Input.Password autoComplete="off" placeholder="请再次输入新密码" /></Form.Item>
<Form.Item wrapperCol={{ offset: 10, span: 24 }}><Space style={{ justifyContent: 'center' }} size={20}><Button onClick={() => setPasswordModal(false)}>取消</Button><Button type="primary" htmlType="submit">提交</Button></Space></Form.Item>
</Form>
</Drawer>
<Drawer placement="right" onClose={() => setShowModal(false)} open={showModal} mask={false}>
<Form form={form} onFinish={submitTeacher} 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: '请输入正确格式的手机号', trigger: 'blur' }]}><Input autoComplete="off" placeholder="请输入手机号" /></Form.Item>
<Form.Item name="role_id" label="角色" rules={[{ required: true, message: '请选择角色' }]}><Select placeholder="请选择角色" showSearch optionFilterProp="children">{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 /></Form.Item>
{isAdd && <Form.Item name="password" label="初始密码" rules={passwordRules}><Input.Password autoComplete="off" placeholder="请输入密码" /></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>
<Drawer onClose={() => setIsEditValidity(false)} open={isEditValidity} footer={null} mask={false} keyboard={false}>
<Row align="middle" justify="center"><Col span={20}><Form form={systemForm} onFinish={submitSystem} labelCol={{ span: 9 }} wrapperCol={{ span: 26 }}>
<Form.Item label="老师分成百分比(%)" name="set_value" style={{ marginBottom: 10 }}><Input autoComplete="off" style={{ width: '100%' }} placeholder="请输入老师分成百分比" /></Form.Item>
<Form.Item wrapperCol={{ offset: 0, span: 26 }}><div style={{ color: '#999999', fontSize: 12 }}>老师佣金计算(预计总收入),编写书籍售卖总金额×分成比例 (范围1-100)</div></Form.Item>
<Form.Item wrapperCol={{ offset: 9, span: 26 }}><Space size={20}>{has('/teacher/list/systemEdit') && <Button onClick={() => setIsEditValidity(false)}>取消</Button>}{has('/teacher/list/systemEdit') && <Button type="primary" htmlType="submit" onClick={() => setIsEditValidity(false)}>提交</Button>}</Space></Form.Item>
</Form></Col></Row>
</Drawer>
</div>
}
......@@ -3,7 +3,7 @@ import { Navigate, useRoutes } from "react-router";
import ModuleBoundary from "@/app/ModuleBoundary";
const TeachersLayout = lazy(() => import("@/pages/teacher/index"));
const TeacherList = lazy(() => import("@/pages/teacher/query/index"));
const TeacherListRoutes = lazy(() => import("./list/routes"));
export default function TeachersRoutes() {
const routes = useRoutes([
......@@ -11,7 +11,7 @@ export default function TeachersRoutes() {
Component: TeachersLayout,
children: [
{ index: true, element: <Navigate to="/teacher/list" /> },
{ path: "list", Component: TeacherList },
{ path: "list/*", Component: TeacherListRoutes },
],
},
{ path: "*", element: <Navigate to="/" /> },
......
import React, { useEffect, useState, useRef } from "react";
import {
Table,
Input,
Button,
Row,
Col,
Space,
DatePicker,
Popover,
Drawer,
Form,
Modal,
Switch,
Select,
Image,
} from "antd";
import { useNavigate } from "react-router";
import PaginationCom from "@/common/Pagination";
import {
getTeacherList,
addTeacher,
TeacherInfo,
editTeacher,
delTeacher,
changeStatus,
changePassword,
getSystem,
systemEdit,
} from "./request.js";
import { getAllList } from "../../jurisdiction/role/request.js";
import dayjs from "dayjs";
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 TableCom from "@/common/TableCom/index";
import { useSelector } from "react-redux";
const { RangePicker } = DatePicker;
const TeacherModule = () => {
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 [form1] = Form.useForm();
const [form2] = Form.useForm();
const [isEditValidity, setIsEditValidity] = useState(false);
// 获取操作权限
const { operationPermissionsList } = useSelector((state) => state.user);
const [initialValues, setinitialValues] = useState({
real_name: "",
tel: "",
validity: "",
password: "",
role_id: null,
});
const [defaultPassword, setDefalutPassword] = useState({
password: "",
re_password: "",
});
const flag = useRef(true);
// 角色列表
const [roleList, setRoleList] = useState([]);
const [filterObj, setfilterObj] = useState({
real_name: "",
tel: "",
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: "" });
setDateVal("");
init();
};
const handleStatus = async (val, id) => {
const bool = await changeStatus({ id, status: val ? "1" : "0" });
bool && init();
};
const init = async (obj = {}) => {
setLoading(true);
const { total, list } = await getTeacherList({ page, page_size, ...obj });
setTotal(total);
setData(list);
setLoading(false);
};
const [id, setId] = useState(0);
const handleEdit = async (obj) => {
const data = await TeacherInfo({ id: obj.id });
let { validity, id } = data;
setIsAdd(false);
setshowModal(true);
form.setFieldsValue({ ...data, validity: dayjs(validity) });
setId(id);
};
const onClose = () => {
setshowModal(false);
};
const submitForm = async (obj) => {
let bool;
let { validity, password } = obj;
console.log(12313);
if (isAdd) {
bool = await addTeacher({
...obj,
validity: dayjs(dayjs(validity).format("YYYY-MM-DD") + " 23:59:59").unix(),
password,
});
} else {
bool = await editTeacher({
...obj,
id,
validity: dayjs(dayjs(validity).format("YYYY-MM-DD") + " 23:59:59").unix(),
});
}
if (!bool) return;
init();
setshowModal(false);
};
// 修改密码
const handleChangePassword = async (obj) => {
let { password: pas, re_password: repas } = obj;
const bool = await changePassword({ id, password: pas, re_password: repas });
bool && init();
setPasswordModal(false);
};
const delSuccess = async () => {
const bool = await delTeacher({ id });
bool && init();
setDelModal(false);
};
const passwordSuccess = () => {
setPasswordModal(false);
};
// 获取角色
const getRoleList = async () => {
const res = await getAllList({ type: 2 });
setRoleList(res);
};
useEffect(() => {
if (!showModal) {
form.setFieldsValue(initialValues);
form.resetFields();
}
if (!passwordModal) {
form2.setFieldsValue(defaultPassword);
form2.resetFields();
}
}, [showModal, passwordModal]);
useEffect(() => {
getRoleList();
}, []);
// 获取设置信息
const getInfo = async () => {
const data = await getSystem({ type: 1 });
form1.setFieldsValue({
set_value: data.set_value,
});
};
// 设置个性化信息
const handleSystemEdit = async (values) => {
const { set_value } = values; // 获取输入框的内容
const data = { type: 1, set_value }; // 构造传递给systemEdit的参数对象
const bool = await systemEdit(data);
console.log("bool", bool);
bool && getInfo();
};
useEffect(() => {
if (operationPermissionsList.includes("/teacher/list/getSystem")) {
getInfo();
}
}, [operationPermissionsList]);
const columns = [
{
title: "ID",
key: "id",
dataIndex: "id",
},
{
title: "老师名称",
key: "real_name",
dataIndex: "real_name",
align: "center",
width: 200,
render: (text) => (
<Popover content={text} overlayStyle={{ maxWidth: "200px" }}>
<div
style={{
display: "-webkit-box",
WebkitLineClamp: "2",
WebkitBoxOrient: "vertical",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{text}
</div>
</Popover>
),
},
{
title: "手机号",
align: "center",
key: "tel",
dataIndex: "tel",
},
{
title: "状态",
key: "status",
dataIndex: "status",
render(_, { id, status }) {
return <Switch defaultChecked={status} onChange={(ev) => handleStatus(ev, id)} />;
},
},
{
title: "有效期",
align: "center",
key: "validity",
dataIndex: "validity",
},
{
title: "创建时间",
align: "center",
key: "create_time",
dataIndex: "create_time",
},
{
title: "操作",
key: "handle",
align: "center",
dataIndex: "handle",
render: (_, record) => {
return (
<Space>
{operationPermissionsList.includes("/teacher/list/getInfoById") && (
<Button onClick={() => handleEdit(record)}>编辑</Button>
)}
{operationPermissionsList.includes("/teacher/list/del") && (
<Button
onClick={() => {
setId(record.id);
setDelModal(true);
}}
>
删除
</Button>
)}
{operationPermissionsList.includes("/teacher/list/editPass") && (
<Button
onClick={() => {
setId(record.id);
setPasswordModal(true);
}}
>
修改密码
</Button>
)}
</Space>
);
},
},
];
return (
<div>
{/* <Space style={{ paddingBottom: '40px' }}>
<label htmlFor='name'>老师分成百分比(%)</label>
<Input
autoComplete='off'
allowClear
value={filterObj.name}
onChange={(ev) => handleFilter('name', ev.target.value)}
placeholder='请输入老师分成百分比'
id='name'
></Input>
<span style={{ marginLeft: '10px', color: '#999999' }}>
老师佣金计算(预计总收入),编写书籍售卖总金额+分成比例
</span>
</Space> */}
<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="创建时间">
<RangePicker
value={dateVal}
onChange={(ev) => handleFilter("startandend", ev)}
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);
console.log(filterObj);
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>
</Space>
</Form>
</Col>
<Col>
<Space>
{operationPermissionsList.includes("/teacher/list/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>
)}
{operationPermissionsList.includes("/teacher/list/getSystem") && (
<Button
type="primary"
ghost
onClick={() => {
setIsAdd(true);
setIsEditValidity(true);
}}
>
分成设置
</Button>
)}
</Space>
</Col>
</Row>
<br />
{TableCom({ columns, loading, data })}
<Modal
mask={false}
centered
open={delMoadal}
onOk={delSuccess}
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}
onOk={passwordSuccess}
onCancel={() => passwordSuccess(false)}
onClose={() => passwordSuccess(false)}
footer={null}
width={700}
okText="提交"
>
<Form
initialValues={defaultPassword}
labelCol={{ span: 6 }}
form={form2}
wrapperCol={{ span: 12 }}
onFinish={handleChangePassword}
>
<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 style={{ justifyContent: "center" }} 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
initialValues={initialValues}
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="请选择角色" showSearch optionFilterProp="children">
{/* <Select placeholder='请选择角色'> */}
{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 />
</Form.Item>
{isAdd && (
<Form.Item
name="password"
label="初始密码"
rules={[
{ required: true, message: "请输入密码" },
{
// pattern: /^(?:(?=.*[A-Z])(?=.*[a-z])|(?=.*[A-Z])(?=.*\d)|(?=.*[A-Z])(?=.*[~@#%$*_\-+=;:,.?])|(?=.*[a-z])(?=.*\d)|(?=.*[a-z])(?=.*[~@#%$*_\-+=;:,.?])|(?=.*\d)(?=.*[~@#%$*_\-+=;:,.?])).{8,12}$/,
// pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)|(?=.*[a-z])(?=.*\d)(?=.*[~@#%$*_\-+=;:,.?])|(?=.*[A-Z])(?=.*\d)(?=.*[~@#%$*_\-+=;:,.?])|(?=.*[a-z])(?=.*[A-Z])(?=.*[~@#%$*_\-+=;:,.?]).{8,12}$/,
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.Password autoComplete='off' placeholder='请输入密码'/> */}
<Input.Password autoComplete="off" placeholder="请输入密码" />
</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>
<Drawer
onClose={() => setIsEditValidity(false)}
open={isEditValidity}
footer={null}
mask={false}
keyboard={false}
onCancel={() => setIsEditValidity(false)}
>
<Row align="middle" justify="center">
<Col span={20}>
<Form
form={form1}
onFinish={handleSystemEdit}
labelCol={{ span: 9 }}
wrapperCol={{ span: 26 }} // 调整 wrapperCol,使其与 labelCol 对齐
>
<Form.Item label="老师分成百分比(%)" name="set_value" style={{ marginBottom: 10 }}>
<Input
autoComplete="off"
style={{ width: "100%" }} // 使用百分比宽度
placeholder="请输入老师分成百分比"
/>
</Form.Item>
<Form.Item wrapperCol={{ offset: 0, span: 26 }}>
{/* 调整偏移和跨度,使按钮居中 */}
<div style={{ color: "#999999", fontSize: 12 }}>
老师佣金计算(预计总收入),编写书籍售卖总金额×分成比例 (范围1-100)
</div>
</Form.Item>
<Form.Item wrapperCol={{ offset: 9, span: 26 }}>
<Space size={20}>
{operationPermissionsList.includes("/teacher/list/systemEdit") && (
<Button onClick={() => setIsEditValidity(false)}>取消</Button>
)}
{operationPermissionsList.includes("/teacher/list/systemEdit") && (
<Button
type="primary"
htmlType="submit"
onClick={() => setIsEditValidity(false)}
>
提交
</Button>
)}
</Space>
</Form.Item>
</Form>
</Col>
</Row>
</Drawer>
</div>
);
};
export default TeacherModule;
.custom-error-message {
font-size: 10px; /* 调整为合适的文字大小 */
}
import axios from "@/utils/request.js";
import qs from "qs";
import { get } from "lodash-es";
import { message } from "antd";
// 老师列表
const getTeacherList = async (obj) => {
try {
const res = await axios.post("/user/teacher/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 addTeacher = async (obj) => {
try {
const res = await axios.post("/user/teacher/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 TeacherInfo = async (obj) => {
try {
const res = await axios.post("/user/teacher/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 editTeacher = async (obj) => {
try {
const res = await axios.post("/user/teacher/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 delTeacher = async (obj) => {
try {
const res = await axios.post("/user/teacher/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/teacher/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 changePassword = async (obj) => {
try {
const res = await axios.post("/user/teacher/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;
}
};
// 获取设置信息
const getSystem = async (obj) => {
try {
const res = await axios.post("/system/config/getSystem", 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 error;
}
};
// 修改设置
const systemEdit = async (obj) => {
try {
const res = await axios.post("/system/config/systemEdit", 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 error;
}
};
export {
getTeacherList,
addTeacher,
TeacherInfo,
editTeacher,
delTeacher,
changeStatus,
changePassword,
getSystem,
systemEdit,
};
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论