From b2727a6b5fc26acd6a665b40793f155ec39730e0 Mon Sep 17 00:00:00 2001 From: sunqh <253801736@qq.com> Date: Fri, 16 Jan 2026 16:47:12 +0800 Subject: [PATCH] Admin --- src/configs/adminConfig.ts | 12 + src/configs/adminLogConfig.ts | 10 + src/configs/config.ts | 2 +- src/configs/menuConfig.tsx | 32 +- src/configs/routes.ts | 15 + src/configs/staffsConfig.ts | 14 + src/configs/usersConfig.ts | 23 ++ src/layouts/base.ts | 3 +- .../Company/List/components/UserEditModal.tsx | 168 +++++++++++ src/pages/Company/List/index.tsx | 245 +++++++++++++++ src/pages/Login/index.tsx | 24 +- src/pages/Record/Login/index.tsx | 195 ++++++++++++ src/pages/Record/Sys/index.tsx | 263 ++++++++++++++++ .../Staff/List/components/AdminEditModal.tsx | 151 ++++++++++ src/pages/Staff/List/index.tsx | 229 ++++++++++++++ .../User/List/components/UserEditModal.tsx | 160 +++++++++- src/pages/User/List/components/state.ts | 2 +- src/pages/User/List/index.tsx | 280 +++++++++++------- src/services/AdminDepartmentServices.ts | 3 + src/services/AdminGroupServices.ts | 3 + src/services/AdminLogServices.ts | 10 + src/services/AdminServices.ts | 12 + src/services/CompanyServices.ts | 3 + src/services/UserServices.ts | 4 +- src/utils/common.ts | 4 +- src/utils/useRequest.ts | 2 +- vite.config.ts | 2 +- 27 files changed, 1736 insertions(+), 135 deletions(-) create mode 100644 src/configs/adminConfig.ts create mode 100644 src/configs/adminLogConfig.ts create mode 100644 src/configs/staffsConfig.ts create mode 100644 src/configs/usersConfig.ts create mode 100644 src/pages/Company/List/components/UserEditModal.tsx create mode 100644 src/pages/Company/List/index.tsx create mode 100644 src/pages/Record/Login/index.tsx create mode 100644 src/pages/Record/Sys/index.tsx create mode 100644 src/pages/Staff/List/components/AdminEditModal.tsx create mode 100644 src/pages/Staff/List/index.tsx create mode 100644 src/services/AdminDepartmentServices.ts create mode 100644 src/services/AdminGroupServices.ts create mode 100644 src/services/AdminLogServices.ts create mode 100644 src/services/AdminServices.ts create mode 100644 src/services/CompanyServices.ts diff --git a/src/configs/adminConfig.ts b/src/configs/adminConfig.ts new file mode 100644 index 0000000..00a20d0 --- /dev/null +++ b/src/configs/adminConfig.ts @@ -0,0 +1,12 @@ +/** + * 管理员状态 + */ +export const statusObj: any = { + 1: '正常', + 2: '禁用', +}; + +export const statusOptions = [ + { label: '正常', value: '1' }, + { label: '禁用', value: '2' }, +]; diff --git a/src/configs/adminLogConfig.ts b/src/configs/adminLogConfig.ts new file mode 100644 index 0000000..305994b --- /dev/null +++ b/src/configs/adminLogConfig.ts @@ -0,0 +1,10 @@ +/** 状态 */ +export const statusObj: any = { + 1: '成功', + 2: '失败', +}; + +export const statusOptions: any = [ + { label: '成功', value: '1' }, + { label: '失败', value: '2' }, +]; diff --git a/src/configs/config.ts b/src/configs/config.ts index a26b2f5..c0501bd 100644 --- a/src/configs/config.ts +++ b/src/configs/config.ts @@ -13,7 +13,7 @@ export const OSSBaseUrl = 'https://cdn.fzcfkj.com/'; export const headerTabNavHeight = 32; export const DefaultERPName = (() => { - return '易宝赞普惠版后台系统'; + return '易宝赞普惠版后台管理系统'; })(); export const Colors = { diff --git a/src/configs/menuConfig.tsx b/src/configs/menuConfig.tsx index 1a66861..9983d7d 100644 --- a/src/configs/menuConfig.tsx +++ b/src/configs/menuConfig.tsx @@ -1,4 +1,4 @@ -import { DashboardOutlined, UserOutlined } from '@ant-design/icons'; +import { BarsOutlined, DashboardOutlined, TeamOutlined, UserOutlined } from '@ant-design/icons'; import type React from 'react'; /* cspell:disable */ @@ -19,20 +19,23 @@ interface MenuDataItem { const userMenu: MenuDataItem = { name: '用户管理', icon: , - children: [ - { name: '用户管理', path: '/user/list', auth: '' }, - // { name: '岗位角色', path: '/user/group', auth: 'SF_ERP_GROUP_VIEW' }, - ], + children: [{ name: '用户信息', path: '/user/list', auth: '' }], //SF_ERP_USER_VIEW }; -const staffMenu: MenuDataItem = { - name: '后台用户', - icon: , +const companyMenu: MenuDataItem = { + name: '企业管理', + icon: , + children: [{ name: '企业信息', path: '/company/list', auth: '' }], //SF_ERP_COMPANY_VIEW +}; +const authMenu: MenuDataItem = { + name: '权限管理', + icon: , children: [ - { name: '组织架构', path: '/staff/dep', auth: 'SF_ERP_DEPART_VIEW' }, - { name: '岗位角色', path: '/staff/group', auth: 'SF_ERP_GROUP_VIEW' }, - { name: '员工管理', path: '/staff/list', auth: 'SF_ERP_STAFF_VIEW' }, - { name: '我的权限', path: '/staff/my', auth: 'SF_MY_RIGHT_VIEW' }, - // { name: '日志管理', path: '/system/record', auth: 'SF_ERP_LOG_VIEW' }, + { name: '管理员信息', path: '/staff/list', auth: '' }, //SF_ERP_ADMIN_VIEW + { name: '组织架构', path: '/staff/dep', auth: '' }, //SF_ERP_DEPART_VIEW + { name: '岗位角色', path: '/staff/group', auth: '' }, //SF_ERP_GROUP_VIEW + { name: '我的权限', path: '/staff/my', auth: '' }, //SF_MY_RIGHT_VIEW + { name: '登录日志', path: '/staff/login', auth: '' }, //SF_LOGIN_LOG_VIEW + { name: '操作日志', path: '/staff/sys', auth: '' }, //SF_OPERATE_LOG_VIEW ], }; @@ -44,7 +47,8 @@ const asideMenuConfig: MenuDataItem[] = [ children: [{ name: '系统主页', path: '/home/index', auth: '' }], }, userMenu, - staffMenu, + companyMenu, + authMenu, ]; export { asideMenuConfig }; diff --git a/src/configs/routes.ts b/src/configs/routes.ts index 39f0d89..672dc52 100644 --- a/src/configs/routes.ts +++ b/src/configs/routes.ts @@ -1,10 +1,14 @@ import { lazy } from 'react'; import AppLayout from '@/layouts/AppLayout'; import EmptyLayout from '@/layouts/EmptyLayout'; +import CompanyList from '@/pages/Company/List'; import ErrorPage from '@/pages/Error'; import Index from '@/pages/Index'; +import Login from '@/pages/Record/Login'; +import Sys from '@/pages/Record/Sys'; import Dep from '@/pages/Staff/dep'; import Group from '@/pages/Staff/group'; +import StaffList from '@/pages/Staff/List'; import UserList from '@/pages/User/List'; import type { IRouteItem } from '@/router/types'; @@ -26,12 +30,23 @@ export const routes: IRouteItem[] = [ // { path: '/group', Component: lazy(() => import('@/pages/Staff/group')) }, ], }, + { + path: '/company', + Layout: AppLayout, + children: [ + { path: '/list', Component: CompanyList }, + // { path: '/group', Component: lazy(() => import('@/pages/Staff/group')) }, + ], + }, { path: '/staff', Layout: AppLayout, children: [ + { path: '/list', Component: StaffList }, { path: '/dep', Component: Dep }, { path: '/group', Component: Group }, + { path: '/login', Component: Login }, + { path: '/sys', Component: Sys }, ], }, { diff --git a/src/configs/staffsConfig.ts b/src/configs/staffsConfig.ts new file mode 100644 index 0000000..80b5e14 --- /dev/null +++ b/src/configs/staffsConfig.ts @@ -0,0 +1,14 @@ +/** 员工类型 */ +export const staffType: any = { + 1: '主账号', + 2: '子账号', + 3: '经销商主账号', + 4: '经销商子账号', +}; + +export const staffTypeOption = [ + { label: '主账号', value: '1' }, + { label: '子账号', value: '2' }, + { label: '经销商主账号', value: '3' }, + { label: '经销商子账号', value: '4' }, +]; diff --git a/src/configs/usersConfig.ts b/src/configs/usersConfig.ts new file mode 100644 index 0000000..42e907c --- /dev/null +++ b/src/configs/usersConfig.ts @@ -0,0 +1,23 @@ +/** 性别 */ +export const userSex: any = { + 1: '男', + 2: '女', +}; + +export const userSexOptions = [ + { label: '男', value: '1' }, + { label: '女', value: '2' }, +]; + +/** + * 用户状态 + */ +export const userState: any = { + 1: '正常', + 0: '禁用', +}; + +export const stateOptions = [ + { label: '正常', value: '1' }, + { label: '禁用', value: '0' }, +]; diff --git a/src/layouts/base.ts b/src/layouts/base.ts index 6638eed..195e96e 100644 --- a/src/layouts/base.ts +++ b/src/layouts/base.ts @@ -1,8 +1,9 @@ +import { AdminServices } from '@/services/AdminServices'; import { requestLite } from '@/utils/useRequest'; export const loginState = async () => { return new Promise((resolve, reject) => { - requestLite('/Users/loginState').then((res) => { + requestLite(AdminServices.loginState).then((res) => { if (res?.err_code == 0) { resolve(res); } else { diff --git a/src/pages/Company/List/components/UserEditModal.tsx b/src/pages/Company/List/components/UserEditModal.tsx new file mode 100644 index 0000000..c25fe6f --- /dev/null +++ b/src/pages/Company/List/components/UserEditModal.tsx @@ -0,0 +1,168 @@ +import { Button, Form, Input, notification, Select } from 'antd'; +import TextArea from 'antd/lib/input/TextArea'; +import { stringify } from 'qs'; +import type React from 'react'; +import { useImperativeHandle, useState } from 'react'; +import ModalPlugin from '@/components/ModalPlugin'; +import { stateOptions, userSexOptions } from '@/configs/usersConfig'; +import { UserServices } from '@/services/UserServices'; +import type { IRef } from '@/utils/type'; +import { useRequest } from '@/utils/useRequest'; + +interface IProps extends IRef { + onCallback?: () => void; +} + +export type IUserEditModalType = { + show: (data?: any) => void; +}; + +export const UserEditModal: React.FC = (props) => { + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(''); + const [form] = Form.useForm(); + const [data, setData] = useState(null); + + // 请求成功回调 + const success = (res: any) => { + if (res.err_code === 0) { + notification.success({ message: '保存成功' }); + props.onCallback?.(); + setOpen(false); + form.resetFields(); // 提交成功重置表单 + } + }; + + const { loading: addLoading, request: addRequest } = useRequest(UserServices.add, { onSuccess: success }); + const { loading: editLoading, request: editRequest } = useRequest(UserServices.edit, { onSuccess: success }); + + const save = async () => { + try { + const values = await form.validateFields(); + // 编辑场景带上主键 + if (data?.user_id) { + values.user_id = data.user_id; + editRequest(stringify(values)); + } else { + addRequest(stringify(values)); + } + } catch (error) { + console.log('表单验证未通过', error); + } + }; + + useImperativeHandle(props.ref, () => ({ + show: (data?: any) => { + setTitle(data ? `${data.login_name} 编辑` : '新增'); + setOpen(true); + setData(data); + + if (data) { + // 编辑场景初始化表单 + form.setFieldsValue({ + login_name: data.login_name, + user_phone: data.user_phone, + user_mail: data.user_mail, + nick_name: data.nick_name, + password: '', // 密码编辑可选 + comments: data.comments, + user_sex: String(data.user_sex), + state: String(data.state), + }); + } else { + form.resetFields(); + form.setFieldsValue({ + user_sex: '1', + state: '1', + }); + } + }, + })); + + return ( + setOpen(false)} + title={title} + footer={[ + setOpen(false)}> + 取消 + , + + 保存 + , + ]} + > + + + + + + + + + + + + + + + + + + + + + + {!data?.user_id && ( + + + + )} + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/Company/List/index.tsx b/src/pages/Company/List/index.tsx new file mode 100644 index 0000000..9aebd7c --- /dev/null +++ b/src/pages/Company/List/index.tsx @@ -0,0 +1,245 @@ +import { Button, DatePicker, Input, Select } from 'antd'; +import { stringify } from 'qs'; +import { useEffect, useRef, useState } from 'react'; +import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; +import { GapBox } from '@/components/GapBox'; +import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin'; +import { FooterPagination, HeaderPagination } from '@/components/PaginationPlugin'; +import { MoreSearchButton, SearchButton } from '@/components/SearchButton'; +import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin'; +import { TablePlugin } from '@/components/TablePlugin'; +import { staffType } from '@/configs/staffsConfig'; +import { stateOptions, userSex, userState } from '@/configs/usersConfig'; +import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common'; +import { type IUserEditModalType, UserEditModal } from '@/pages/User/List/components/UserEditModal'; +import { UserServices } from '@/services/UserServices'; +import { useAuthStore } from '@/store/AuthStore'; +import { tableFixedByPhone, toArray } from '@/utils/common'; +import { useRequest } from '@/utils/useRequest'; + +interface IAjaxData extends IAjaxDataBase { + data: any[]; +} +type IParams = IParamsBase & { + login_name?: string; + nick_name?: string; + user_phone?: string; + state?: number; + create_dateL?: string; + create_dateU?: string; +}; + +/** 企业列表页面 */ +const CompanyListForm: React.FC = () => { + const auth = useAuthStore().auth; + const [params, setParams] = useState({ curr_page: 1, page_count: 20 }); + const [ajaxData, setAjaxData] = useState({ count: 0, data: [] }); + const [showMoreSearch, setShowMoreSearch] = useState(false); + const UserEditModalRef = useRef(null); + + const { loading: userLoading, request: userRequest } = useRequest(UserServices.getUserList, { + onSuccessCodeZero: (res) => { + setAjaxData({ + count: res.count || 0, + data: toArray(res.data), + }); + }, + }); + + const columns: ColumnsTypeUltra = [ + { + title: '操作', + width: 50, + fixed: tableFixedByPhone('left'), + render: (_, item) => ( + + { + UserEditModalRef.current?.show(item); + }} + > + 编辑 + + {auth.SF_ERP_USER_EDIT && ( + { + UserEditModalRef.current?.show(item); + }} + > + 编辑 + + )} + + ), + }, + { title: '用户名', dataIndex: 'login_name', width: 120 }, + { title: '企业名称', dataIndex: 'company_name', width: 120 }, + { title: '类型', dataIndex: 'staff_type', width: 60, render: (value) => staffType[value] }, + { title: '昵称', dataIndex: 'nick_name', width: 120 }, + { title: '手机', dataIndex: 'user_phone', width: 120 }, + { title: '性别', dataIndex: 'user_sex', width: 60, render: (value) => userSex[value] }, + { title: '状态', dataIndex: 'state', width: 60, ellipsis: true, render: (value) => userState[value] }, + { + title: '备注', + dataIndex: 'comments', + width: 300, + }, + { + title: '创建时间', + width: window.dfConfig.isPhone ? 160 : 160, + dataIndex: 'create_date', + ellipsis: true, + }, + ]; + + function page(curr: number) { + if (!userLoading) { + params.curr_page = curr; + console.log(params); + userRequest(stringify(params)); + } + } + + useEffect(() => { + page(1); + }, []); + + return ( + <> + + + { + params.login_name = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.nick_name = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.user_phone = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + + + { + page(1); + }} + /> + { + setShowMoreSearch((v) => !v); + }} + /> + + + + + + + { + params.state = value || undefined; + setParams({ ...params }); + }} + /> + + + { + params.create_dateL = dates?.[0] || undefined; + params.create_dateU = dates?.[1] || undefined; + }} + /> + + + + + + { + UserEditModalRef.current?.show(); + }} + > + 新增 + + + + + + { + page(1); + }} + dataSource={ajaxData.data} + columns={columns} + scroll={{ x: true }} + rowKey={'user_id'} + /> + + { + params.page_count = pageSize; + page(curr); + }} + /> + + page(1)} /> + > + ); +}; + +const CompanyList = () => ( + + + +); +export default CompanyList; diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx index 24a1a07..33d73f2 100644 --- a/src/pages/Login/index.tsx +++ b/src/pages/Login/index.tsx @@ -4,14 +4,22 @@ import { useState } from 'react'; import loginBg from '@/assets/loginBg.jpg'; import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; import { DefaultERPName } from '@/configs/config'; +import { AdminServices } from '@/services/AdminServices'; +import { type IAuth, useAuthStore } from '@/store/AuthStore'; +import { useUserStore } from '@/store/UserStore'; +import { toObject } from '@/utils/common'; import { notificationEventBus } from '@/utils/EventBus'; import { useRequest } from '@/utils/useRequest'; const Login = () => { - const [userInfo, setUserInfo] = useState({ login_name: 'zhengw', password: '123456', login_type: 1 }); - const { request, loading } = useRequest('Users/login', { - onSuccessCodeZero() { + const user = useUserStore(); + const auth = useAuthStore(); + const [userInfo, setUserInfo] = useState({ username: 'admin', password: '123456', login_type: 1 }); + const { request, loading } = useRequest(AdminServices.login, { + onSuccessCodeZero: (res) => { notificationEventBus.emit({ description: '登录成功' }); + user.updateUser(res.user_info); + auth.updateAuth(toObject(res.auth) as IAuth); location.href = '#/'; }, }); @@ -45,16 +53,20 @@ const Login = () => { background: 'rgba(255,255,255,0.8)', }} > - {DefaultERPName}后台登录 + + {DefaultERPName} + {document.title} + { - userInfo.login_name = e.target.value; + userInfo.username = e.target.value; setUserInfo({ ...userInfo }); }} onPressEnter={login} diff --git a/src/pages/Record/Login/index.tsx b/src/pages/Record/Login/index.tsx new file mode 100644 index 0000000..0557073 --- /dev/null +++ b/src/pages/Record/Login/index.tsx @@ -0,0 +1,195 @@ +import { DatePicker, Input, Select } from 'antd'; +import { stringify } from 'qs'; +import { useEffect, useState } from 'react'; +import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; +import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin'; +import { FooterPagination, HeaderPagination } from '@/components/PaginationPlugin'; +import { MoreSearchButton, SearchButton } from '@/components/SearchButton'; +import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin'; +import { TablePlugin } from '@/components/TablePlugin'; +import { statusObj, statusOptions } from '@/configs/adminLogConfig'; +import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common'; +import { AdminLogServices } from '@/services/AdminLogServices'; +import { useAuthStore } from '@/store/AuthStore'; +import { tableFixedByPhone, toArray } from '@/utils/common'; +import { useRequest } from '@/utils/useRequest'; + +interface IAjaxData extends IAjaxDataBase { + data: any[]; +} +type IParams = IParamsBase & { + ip?: string; + username?: string; + status?: number; + login_dateL?: string; + login_dateU?: string; +}; + +/** 登录日志页面 */ +const AdminLoginLogForm: React.FC = () => { + const auth = useAuthStore().auth; + const [params, setParams] = useState({ curr_page: 1, page_count: 20 }); + const [ajaxData, setAjaxData] = useState({ count: 0, data: [] }); + const [showMoreSearch, setShowMoreSearch] = useState(false); + + const { loading: listLoading, request: listRequest } = useRequest(AdminLogServices.getAdminLoginLogList, { + onSuccessCodeZero: (res) => { + setAjaxData({ + count: res.count || 0, + data: toArray(res.data), + }); + }, + }); + + const columns: ColumnsTypeUltra = [ + { + title: '#', + width: 20, + fixed: tableFixedByPhone('left'), + align: 'center', + render(_value, _record, index) { + return index + 1; + }, + }, + { title: '管理员账号', dataIndex: 'username', width: 120 }, + { title: '登录IP', dataIndex: 'ip', width: 120 }, + { title: '登录地址', dataIndex: 'ip_location', width: 120 }, + { title: '平台', dataIndex: 'os', width: 120 }, + { title: '浏览器', dataIndex: 'browser', width: 120 }, + { title: '登录时间', dataIndex: 'login_date', width: 120 }, + { title: '状态', dataIndex: 'status', width: 60, render: (value) => statusObj[value] }, + { + title: '备注', + dataIndex: 'remark', + width: 120, + }, + { + title: '创建时间', + width: window.dfConfig.isPhone ? 160 : 160, + dataIndex: 'create_date', + }, + ]; + + function page(curr: number) { + if (!listLoading) { + params.curr_page = curr; + listRequest(stringify(params)); + } + } + + useEffect(() => { + page(1); + }, []); + + return ( + <> + + + { + params.username = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.ip = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.status = value || undefined; + setParams({ ...params }); + }} + /> + + + + { + page(1); + }} + /> + { + setShowMoreSearch((v) => !v); + }} + /> + + + + + + + { + params.login_dateL = dates?.[0] || undefined; + params.login_dateU = dates?.[1] || undefined; + }} + /> + + + + + + { + page(1); + }} + dataSource={ajaxData.data} + columns={columns} + scroll={{ x: true }} + rowKey={'login_id'} + /> + + { + params.page_count = pageSize; + page(curr); + }} + /> + > + ); +}; + +/** 登录日志页面 */ +const RecordLogin = () => ( + + + +); +export default RecordLogin; diff --git a/src/pages/Record/Sys/index.tsx b/src/pages/Record/Sys/index.tsx new file mode 100644 index 0000000..86f26de --- /dev/null +++ b/src/pages/Record/Sys/index.tsx @@ -0,0 +1,263 @@ +import { DatePicker, Input, Select, TreeSelect } from 'antd'; +import { stringify } from 'qs'; +import { useEffect, useState } from 'react'; +import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; +import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin'; +import { FooterPagination, HeaderPagination } from '@/components/PaginationPlugin'; +import { MoreSearchButton, SearchButton } from '@/components/SearchButton'; +import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin'; +import { TablePlugin } from '@/components/TablePlugin'; +import { statusObj, statusOptions } from '@/configs/adminLogConfig'; +import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common'; +import { AdminLogServices } from '@/services/AdminLogServices'; +import { useAuthStore } from '@/store/AuthStore'; +import { tableFixedByPhone, toArray } from '@/utils/common'; +import { useRequest } from '@/utils/useRequest'; + +interface IAjaxData extends IAjaxDataBase { + data: any[]; +} +type IParams = IParamsBase & { + ip?: string; + username?: string; + status?: number; + menu_id?: number; + function_id?: number; + create_dateL?: string; + create_dateU?: string; +}; + +/** 操作日志页面 */ +const AdminSysLogForm: React.FC = () => { + const auth = useAuthStore().auth; + const [params, setParams] = useState({ curr_page: 1, page_count: 20 }); + const [ajaxData, setAjaxData] = useState({ count: 0, data: [] }); + const [showMoreSearch, setShowMoreSearch] = useState(false); + + /** 菜单树 */ + function buildTree(list: any[], parentId = 0): any[] { + return list + .filter((item) => item.parent_menu_id == parentId) + .map((item) => ({ + title: item.menu_ch_name, + value: item.menu_id, + key: item.menu_id, + children: buildTree(list, item.menu_id), + })); + } + + const [menuList, setMenuList] = useState([]); + const { request: menuListRequest } = useRequest(AdminLogServices.getAdminMenuAjaxList, { + onSuccess: (res) => { + if (res.err_code === 0) { + const rawList = toArray(res?.data); + setMenuList(buildTree(toArray(rawList))); + } + }, + }); + + /** 当前菜单下的功能 */ + const [functionList, setFunctionList] = useState([]); + const { request: functionListRequest } = useRequest(AdminLogServices.getAdminFunctionAjaxList, { + onSuccess: (res) => { + if (res.err_code === 0) { + const list: any[] = []; + toArray(res.data).forEach((el) => { + list.push({ label: el.function_ch_name, value: el.function_id }); + }); + setFunctionList(list); + } + }, + }); + + const { loading: listLoading, request: listRequest } = useRequest(AdminLogServices.getAdminSysLogList, { + onSuccessCodeZero: (res) => { + setAjaxData({ + count: res.count || 0, + data: toArray(res.data), + }); + }, + }); + + const columns: ColumnsTypeUltra = [ + { + title: '#', + width: 20, + fixed: tableFixedByPhone('left'), + align: 'center', + render(_value, _record, index) { + return index + 1; + }, + }, + { title: '菜单', dataIndex: 'menu_ch_name', width: 80 }, + { title: '权限', dataIndex: 'function_ch_name', width: 60 }, + { title: '操作账号', dataIndex: 'username', width: 120 }, + { title: '操作IP', dataIndex: 'ip', width: 120 }, + { title: '操作时间', dataIndex: 'create_date', width: 120 }, + { title: '状态', dataIndex: 'status', width: 60, render: (value) => statusObj[value] }, + ]; + + function page(curr: number) { + if (!listLoading) { + params.curr_page = curr; + listRequest(stringify(params)); + } + } + + useEffect(() => { + page(1); + menuListRequest(); + }, []); + + return ( + <> + + + { + params.username = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + setParams((prev) => ({ + ...prev, + menu_id: value || undefined, + function_id: undefined, + })); + + setFunctionList([]); + if (value) { + functionListRequest(stringify({ menu_id: value })); + } + }} + /> + + + + { + setParams((prev) => ({ + ...prev, + function_id: value || undefined, + })); + }} + /> + + + + + { + page(1); + }} + /> + { + setShowMoreSearch((v) => !v); + }} + /> + + + + + + + { + params.ip = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.status = value || undefined; + setParams({ ...params }); + }} + /> + + + { + params.create_dateL = dates?.[0] || undefined; + params.create_dateU = dates?.[1] || undefined; + }} + /> + + + + + + { + page(1); + }} + dataSource={ajaxData.data} + columns={columns} + scroll={{ x: true }} + rowKey={'sys_id'} + /> + + { + params.page_count = pageSize; + page(curr); + }} + /> + > + ); +}; + +/** 操作日志页面 */ +const RecordSys = () => ( + + + +); +export default RecordSys; diff --git a/src/pages/Staff/List/components/AdminEditModal.tsx b/src/pages/Staff/List/components/AdminEditModal.tsx new file mode 100644 index 0000000..7d495e4 --- /dev/null +++ b/src/pages/Staff/List/components/AdminEditModal.tsx @@ -0,0 +1,151 @@ +import { Button, Form, Input, notification, Select } from 'antd'; +import { stringify } from 'qs'; +import type React from 'react'; +import { useImperativeHandle, useState } from 'react'; +import ModalPlugin from '@/components/ModalPlugin'; +import { statusOptions } from '@/configs/adminConfig'; +import { AdminServices } from '@/services/AdminServices'; +import type { IRef } from '@/utils/type'; +import { useRequest } from '@/utils/useRequest'; + +interface IProps extends IRef { + onCallback?: () => void; +} + +export type IAdminEditModalType = { + show: (data?: any) => void; +}; + +export const AdminEditModal: React.FC = (props) => { + const [open, setOpen] = useState(false); + const [title, setTitle] = useState(''); + const [form] = Form.useForm(); + const [data, setData] = useState(null); + + // 请求成功回调 + const success = (res: any) => { + if (res.err_code === 0) { + notification.success({ message: '保存成功' }); + props.onCallback?.(); + setOpen(false); + form.resetFields(); // 提交成功重置表单 + } + }; + + const { loading: addLoading, request: addRequest } = useRequest(AdminServices.add, { onSuccess: success }); + const { loading: editLoading, request: editRequest } = useRequest(AdminServices.edit, { onSuccess: success }); + + const save = async () => { + try { + const values = await form.validateFields(); + // 编辑场景带上主键 + if (data?.admin_id) { + values.admin_id = data.admin_id; + editRequest(stringify(values)); + } else { + addRequest(stringify(values)); + } + } catch (error) { + console.log('表单验证未通过', error); + } + }; + + useImperativeHandle(props.ref, () => ({ + show: (data?: any) => { + setTitle(data ? `${data.username} 编辑` : '新增'); + setOpen(true); + setData(data); + + if (data) { + // 编辑场景初始化表单 + form.setFieldsValue({ + username: data.username, + mobile: data.mobile, + email: data.email, + nickname: data.nickname, + password: '', // 密码编辑可选 + status: String(data.status), + }); + } else { + form.resetFields(); + form.setFieldsValue({ + status: '1', + }); + } + }, + })); + + return ( + setOpen(false)} + title={title} + footer={[ + setOpen(false)}> + 取消 + , + + 保存 + , + ]} + > + + + + + + + + + + + + + + + + + + 不填写则系统自动生成初始密码} + rules={[{ min: 6, max: 18, message: '密码需6~18位' }]} + > + + + + + + + + + ); +}; diff --git a/src/pages/Staff/List/index.tsx b/src/pages/Staff/List/index.tsx new file mode 100644 index 0000000..f22957b --- /dev/null +++ b/src/pages/Staff/List/index.tsx @@ -0,0 +1,229 @@ +import { Button, DatePicker, Input, Select } from 'antd'; +import { stringify } from 'qs'; +import { useEffect, useRef, useState } from 'react'; +import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; +import { GapBox } from '@/components/GapBox'; +import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin'; +import { FooterPagination, HeaderPagination } from '@/components/PaginationPlugin'; +import { MoreSearchButton, SearchButton } from '@/components/SearchButton'; +import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin'; +import { TablePlugin } from '@/components/TablePlugin'; +import { statusObj, statusOptions } from '@/configs/adminConfig'; +import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common'; +import { AdminServices } from '@/services/AdminServices'; +import { useAuthStore } from '@/store/AuthStore'; +import { tableFixedByPhone, toArray } from '@/utils/common'; +import { useRequest } from '@/utils/useRequest'; +import { AdminEditModal, type IAdminEditModalType } from './components/AdminEditModal'; + +interface IAjaxData extends IAjaxDataBase { + data: any[]; +} +type IParams = IParamsBase & { + username?: string; + nickname?: string; + mobile?: string; + status?: number; + create_dateL?: string; + create_dateU?: string; +}; + +/** 管理员列表页面 */ +const AdminListForm: React.FC = () => { + const auth = useAuthStore().auth; + const [params, setParams] = useState({ curr_page: 1, page_count: 20 }); + const [ajaxData, setAjaxData] = useState({ count: 0, data: [] }); + const [showMoreSearch, setShowMoreSearch] = useState(false); + const AdminEditModalRef = useRef(null); + + const { loading: listLoading, request: listRequest } = useRequest(AdminServices.getAdminList, { + onSuccessCodeZero: (res) => { + setAjaxData({ + count: res.count || 0, + data: toArray(res.data), + }); + }, + }); + + const columns: ColumnsTypeUltra = [ + { + title: '操作', + width: 50, + fixed: tableFixedByPhone('left'), + render: (_, item) => ( + + {item?.admin_id != 1 && ( + { + AdminEditModalRef.current?.show(item); + }} + > + 编辑 + + )} + + ), + }, + { title: '管理员', dataIndex: 'username', width: 120 }, + { title: '昵称', dataIndex: 'nickname', width: 120 }, + { title: '手机', dataIndex: 'mobile', width: 120 }, + { title: '邮箱', dataIndex: 'email', width: 120 }, + { title: '状态', dataIndex: 'status', width: 60, ellipsis: true, render: (value) => statusObj[value] }, + { + title: '创建时间', + width: window.dfConfig.isPhone ? 160 : 160, + dataIndex: 'create_date', + ellipsis: true, + }, + ]; + + function page(curr: number) { + if (!listLoading) { + params.curr_page = curr; + console.log(params); + listRequest(stringify(params)); + } + } + + useEffect(() => { + page(1); + }, []); + + return ( + <> + + + { + params.username = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.nickname = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + { + params.mobile = e.target.value.trim() || undefined; + }} + onPressEnter={() => page(1)} + /> + + + + + { + page(1); + }} + /> + { + setShowMoreSearch((v) => !v); + }} + /> + + + + + + + { + params.status = value || undefined; + setParams({ ...params }); + }} + /> + + + { + params.create_dateL = dates?.[0] || undefined; + params.create_dateU = dates?.[1] || undefined; + }} + /> + + + + + + { + AdminEditModalRef.current?.show(); + }} + > + 新增 + + + + + + { + page(1); + }} + dataSource={ajaxData.data} + columns={columns} + scroll={{ x: true }} + rowKey={'admin_id'} + /> + + { + params.page_count = pageSize; + page(curr); + }} + /> + + page(1)} /> + > + ); +}; + +const StaffList = () => ( + + + +); +export default StaffList; diff --git a/src/pages/User/List/components/UserEditModal.tsx b/src/pages/User/List/components/UserEditModal.tsx index 1069174..e490ca4 100644 --- a/src/pages/User/List/components/UserEditModal.tsx +++ b/src/pages/User/List/components/UserEditModal.tsx @@ -1,26 +1,174 @@ +import { Button, Form, Input, notification, Select } from 'antd'; +import TextArea from 'antd/lib/input/TextArea'; +import { stringify } from 'qs'; import type React from 'react'; import { useImperativeHandle, useState } from 'react'; import ModalPlugin from '@/components/ModalPlugin'; +import { stateOptions, userSexOptions } from '@/configs/usersConfig'; +import { UserServices } from '@/services/UserServices'; import type { IRef } from '@/utils/type'; +import { useRequest } from '@/utils/useRequest'; -interface IProps extends IRef {} +interface IProps extends IRef { + onCallback?: () => void; +} export type IUserEditModalType = { - show: () => void; + show: (data?: any) => void; }; export const UserEditModal: React.FC = (props) => { - console.log(props.ref); const [open, setOpen] = useState(false); + const [title, setTitle] = useState(''); + const [form] = Form.useForm(); + const [data, setData] = useState(null); + + // 请求成功回调 + const success = (res: any) => { + if (res.err_code === 0) { + notification.success({ message: '保存成功' }); + props.onCallback?.(); + setOpen(false); + form.resetFields(); // 提交成功重置表单 + } + }; + + const { loading: addLoading, request: addRequest } = useRequest(UserServices.add, { onSuccess: success }); + const { loading: editLoading, request: editRequest } = useRequest(UserServices.edit, { onSuccess: success }); + + const save = async () => { + try { + const values = await form.validateFields(); + // 编辑场景带上主键 + if (data?.user_id) { + values.user_id = data.user_id; + editRequest(stringify(values)); + } else { + addRequest(stringify(values)); + } + } catch (error) { + console.log('表单验证未通过', error); + } + }; useImperativeHandle(props.ref, () => ({ - show: () => { + show: (data?: any) => { + setTitle(data ? `${data.login_name} 编辑` : '新增'); setOpen(true); + setData(data); + + if (data) { + // 编辑场景初始化表单 + form.setFieldsValue({ + login_name: data.login_name, + user_phone: data.user_phone, + user_mail: data.user_mail, + nick_name: data.nick_name, + password: '', // 密码编辑可选 + comments: data.comments, + user_sex: String(data.user_sex), + state: String(data.state), + }); + } else { + form.resetFields(); + form.setFieldsValue({ + user_sex: '1', + state: '1', + }); + } }, })); + return ( - setOpen(false)}> - 11111 + setOpen(false)} + title={title} + footer={[ + setOpen(false)}> + 取消 + , + + 保存 + , + ]} + > + + + + + + + + + + + + + + + + + + 不填写则系统自动生成初始密码} + rules={[{ min: 6, max: 18, message: '密码需6~18位' }]} + > + + + + {!data?.user_id && ( + + + + )} + + + + + + + + + + + + + ); }; diff --git a/src/pages/User/List/components/state.ts b/src/pages/User/List/components/state.ts index 413fd64..4227311 100644 --- a/src/pages/User/List/components/state.ts +++ b/src/pages/User/List/components/state.ts @@ -73,7 +73,7 @@ export const userListStateProxy = proxy<{ if (temp.order_step?.length) { temp.order_step = temp.order_step.join(','); } - const res: any = await requestLite(UserServices.list, temp); + const res: any = await requestLite(UserServices.getUserList, temp); this.loading = false; this.ajaxData = toArray(res?.data); if (this.ajaxData.length == 0 && this.params.curr_page > 1) { diff --git a/src/pages/User/List/index.tsx b/src/pages/User/List/index.tsx index f00ab1c..679f20a 100644 --- a/src/pages/User/List/index.tsx +++ b/src/pages/User/List/index.tsx @@ -1,116 +1,196 @@ -import { Button } from 'antd'; -import { useRef } from 'react'; -import { useSnapshot } from 'valtio'; +import { Button, DatePicker, Input, Select } from 'antd'; +import { stringify } from 'qs'; +import { useEffect, useRef, useState } from 'react'; import { FormItemPlugin, FormPlugin } from '@/components/FormPlugin'; import { GapBox } from '@/components/GapBox'; import PageContainerPlugin from '@/components/PageContainer/PageContainerPlugin'; import { FooterPagination, HeaderPagination } from '@/components/PaginationPlugin'; -import { MoreSearchButton, ResetButton, SearchButton, SearchInputPlugin } from '@/components/SearchButton'; -import type { ColumnsTypeUltra2 } from '@/components/TableColumnsFilterPlugin2'; -import { formatTableSort, TablePlugin } from '@/components/TablePlugin'; -import { tableFixedByPhone } from '@/utils/common'; -import { userListStateProxy } from './components/state'; -import { type IUserEditModalType, UserEditModal } from './components/UserEditModal'; +import { MoreSearchButton, SearchButton } from '@/components/SearchButton'; +import type { ColumnsTypeUltra } from '@/components/TableColumnsFilterPlugin'; +import { TablePlugin } from '@/components/TablePlugin'; +import { staffType } from '@/configs/staffsConfig'; +import { stateOptions, userSex, userState } from '@/configs/usersConfig'; +import type { IAjaxDataBase, IParamsBase } from '@/interfaces/common'; +import { type IUserEditModalType, UserEditModal } from '@/pages/User/List/components/UserEditModal'; +import { UserServices } from '@/services/UserServices'; +import { useAuthStore } from '@/store/AuthStore'; +import { tableFixedByPhone, toArray } from '@/utils/common'; +import { useRequest } from '@/utils/useRequest'; -const SearchBox = () => { - const snap = useSnapshot(userListStateProxy); +interface IAjaxData extends IAjaxDataBase { + data: any[]; +} +type IParams = IParamsBase & { + login_name?: string; + nick_name?: string; + user_phone?: string; + state?: number; + create_dateL?: string; + create_dateU?: string; +}; + +/** 用户列表页面 */ +const UserListForm: React.FC = () => { + const auth = useAuthStore().auth; + const [params, setParams] = useState({ curr_page: 1, page_count: 20 }); + const [ajaxData, setAjaxData] = useState({ count: 0, data: [] }); + const [showMoreSearch, setShowMoreSearch] = useState(false); + const UserEditModalRef = useRef(null); + + const { loading: userLoading, request: userRequest } = useRequest(UserServices.getUserList, { + onSuccessCodeZero: (res) => { + setAjaxData({ + count: res.count || 0, + data: toArray(res.data), + }); + }, + }); + + const columns: ColumnsTypeUltra = [ + { + title: '操作', + width: 50, + fixed: tableFixedByPhone('left'), + render: (_, item) => ( + + { + UserEditModalRef.current?.show(item); + }} + > + 编辑 + + {auth.SF_ERP_USER_EDIT && ( + { + UserEditModalRef.current?.show(item); + }} + > + 编辑 + + )} + + ), + }, + { title: '用户名', dataIndex: 'login_name', width: 120 }, + { title: '企业名称', dataIndex: 'company_name', width: 120 }, + { title: '类型', dataIndex: 'staff_type', width: 60, render: (value) => staffType[value] }, + { title: '昵称', dataIndex: 'nick_name', width: 120 }, + { title: '手机', dataIndex: 'user_phone', width: 120 }, + { title: '性别', dataIndex: 'user_sex', width: 60, render: (value) => userSex[value] }, + { title: '状态', dataIndex: 'state', width: 60, ellipsis: true, render: (value) => userState[value] }, + { + title: '备注', + dataIndex: 'comments', + width: 300, + }, + { + title: '创建时间', + width: window.dfConfig.isPhone ? 160 : 160, + dataIndex: 'create_date', + ellipsis: true, + }, + ]; + + function page(curr: number) { + if (!userLoading) { + params.curr_page = curr; + console.log(params); + userRequest(stringify(params)); + } + } + + useEffect(() => { + page(1); + }, []); return ( <> - - { - userListStateProxy.page(1); - }} - onEnd={(v) => { - userListStateProxy.params.order_no = v; + + { + params.login_name = e.target.value.trim() || undefined; }} + onPressEnter={() => page(1)} /> - - { - userListStateProxy.page(1); - }} - onEnd={(v) => { - userListStateProxy.params.custom_order_no = v; + + { + params.nick_name = e.target.value.trim() || undefined; }} + onPressEnter={() => page(1)} /> - - { - userListStateProxy.page(1); - }} - onEnd={(v) => { - userListStateProxy.params.custom_name = v; + + { + params.user_phone = e.target.value.trim() || undefined; }} + onPressEnter={() => page(1)} /> - + { - userListStateProxy.page(1); - }} - /> - { - userListStateProxy.reset(); + page(1); }} /> { - userListStateProxy.showMoreSearch = !userListStateProxy.showMoreSearch; + setShowMoreSearch((v) => !v); }} /> - + - - - { - userListStateProxy.page(1); + + + + { + params.state = value || undefined; + setParams({ ...params }); }} - onEnd={(v) => { - userListStateProxy.params.custom_phone = v; + /> + + + { + params.create_dateL = dates?.[0] || undefined; + params.create_dateU = dates?.[1] || undefined; }} /> - > - ); -}; -const Content = () => { - const snap = useSnapshot(userListStateProxy); - const tableColumns: ColumnsTypeUltra2 = [ - { - columnName: '操作', - title: '操作', - fixed: tableFixedByPhone('left'), - width: 52, - render: () => { - return {/* */}; - }, - }, - { columnName: '创建时间', width: 150, title: '创建时间', dataIndex: 'create_date', sorter: true }, - { columnName: '备注', title: '备注', dataIndex: 'comments', width: 180 }, - { columnName: '' }, - ]; - - const UserEditModalRef = useRef(null); - - return ( - <> { UserEditModalRef.current?.show(); }} > - 新增用户 + 新增 { - userListStateProxy.page(page); - }} + style={{ marginBottom: 0, marginRight: 12 }} + current={params.curr_page} + pageSize={params.page_count} + total={ajaxData.count} + onChange={page} /> + { - userListStateProxy.params.order = formatTableSort(sort); - userListStateProxy.page(1); + page(1); }} - dataSource={snap.ajaxData} + dataSource={ajaxData.data} + columns={columns} + scroll={{ x: true }} rowKey={'user_id'} - columns={tableColumns} /> + { - userListStateProxy.page(page, pageSize); + current={params.curr_page} + pageSize={params.page_count} + total={ajaxData.count} + onChange={(curr, pageSize) => { + params.page_count = pageSize; + page(curr); }} /> - + page(1)} /> > ); }; const UserList = () => ( - - - + + ); - export default UserList; diff --git a/src/services/AdminDepartmentServices.ts b/src/services/AdminDepartmentServices.ts new file mode 100644 index 0000000..9900f33 --- /dev/null +++ b/src/services/AdminDepartmentServices.ts @@ -0,0 +1,3 @@ +export const AdminDepartmentServices = { + getAdminDepartmentList: '/AdminDepartment/getAdminDepartmentList', +} as const; diff --git a/src/services/AdminGroupServices.ts b/src/services/AdminGroupServices.ts new file mode 100644 index 0000000..0df1cb7 --- /dev/null +++ b/src/services/AdminGroupServices.ts @@ -0,0 +1,3 @@ +export const AdminGroupServices = { + getAdminGroupList: '/AdminGroup/getAdminGroupList', +} as const; diff --git a/src/services/AdminLogServices.ts b/src/services/AdminLogServices.ts new file mode 100644 index 0000000..4e0541a --- /dev/null +++ b/src/services/AdminLogServices.ts @@ -0,0 +1,10 @@ +export const AdminLogServices = { + /** 登录日志 */ + getAdminLoginLogList: '/AdminLog/getAdminLoginLogList', + /** 操作日志 */ + getAdminSysLogList: '/AdminLog/getAdminSysLogList', + /** 菜单 */ + getAdminMenuAjaxList: '/AdminMenu/getAdminMenuAjaxList', + /** 权限 */ + getAdminFunctionAjaxList: '/AdminFunction/getAdminFunctionAjaxList', +} as const; diff --git a/src/services/AdminServices.ts b/src/services/AdminServices.ts new file mode 100644 index 0000000..ca5ff52 --- /dev/null +++ b/src/services/AdminServices.ts @@ -0,0 +1,12 @@ +export const AdminServices = { + /** 获取登录状态及信息 */ + loginState: '/Admin/loginState', + /** 退出登录 */ + logout: '/Admin/logout', + /** 管理员登录 */ + login: '/Admin/login', + /** 管理员列表 */ + getAdminList: '/Admin/getAdminList', + add: '/Admin/add', + edit: '/Admin/edit', +} as const; diff --git a/src/services/CompanyServices.ts b/src/services/CompanyServices.ts new file mode 100644 index 0000000..821aebf --- /dev/null +++ b/src/services/CompanyServices.ts @@ -0,0 +1,3 @@ +export const CompanyServices = { + getCompanyList: '/Company/getCompanyList', +} as const; diff --git a/src/services/UserServices.ts b/src/services/UserServices.ts index 8e5c2dc..870614c 100644 --- a/src/services/UserServices.ts +++ b/src/services/UserServices.ts @@ -1,3 +1,5 @@ export const UserServices = { - list: '/UserServices/list', + getUserList: '/User/getUserList', + add: '/User/add', + edit: '/User/edit', } as const; diff --git a/src/utils/common.ts b/src/utils/common.ts index d6d5238..dc2557f 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -37,9 +37,9 @@ export const pathAddApiString = (path: string): string => { return path; } if (`${path}`.startsWith('/')) { - return `/api${path}`; + return `/api/adminrelas${path}`; } - return `/api/${path}`; + return `/api/adminrelas/${path}`; } return path; }; diff --git a/src/utils/useRequest.ts b/src/utils/useRequest.ts index e4e6c24..5e8c7d4 100644 --- a/src/utils/useRequest.ts +++ b/src/utils/useRequest.ts @@ -37,7 +37,7 @@ export const useRequest = (url: string, options?: IOptions) => { const res = await post(url, data, config); if (res?.err_code != 0) { - if (res.err_code == 110000) { + if (res.err_code == 401) { location.href = '#/login'; } else { if (options?.hideNotice != true) { diff --git a/vite.config.ts b/vite.config.ts index 728036c..a3e33ff 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -23,7 +23,7 @@ export default defineConfig({ host: '0.0.0.0', proxy: { '/api': { - target: 'http://192.168.1.138:93', + target: 'http://www.free.loc', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), // 不可以省略rewrite },