@@ -20,7 +20,6 @@ export function isReportMode(): boolean { | |||||
// Read all environment variable configuration files to process.env | // Read all environment variable configuration files to process.env | ||||
export function wrapperEnv(envConf: Recordable): ViteEnv { | export function wrapperEnv(envConf: Recordable): ViteEnv { | ||||
const ret: any = {}; | const ret: any = {}; | ||||
for (const envName of Object.keys(envConf)) { | for (const envName of Object.keys(envConf)) { | ||||
let realName = envConf[envName].replace(/\\n/g, '\n'); | let realName = envConf[envName].replace(/\\n/g, '\n'); | ||||
realName = realName === 'true' ? true : realName === 'false' ? false : realName; | realName = realName === 'true' ? true : realName === 'false' ? false : realName; | ||||
@@ -12,7 +12,7 @@ import { i18n, t } from '@/utils/i18n'; | |||||
import { BellOutlined, MenuOutlined, SettingOutlined } from '@ant-design/icons'; | import { BellOutlined, MenuOutlined, SettingOutlined } from '@ant-design/icons'; | ||||
import { useUserStore } from '@/store/global/user'; | import { useUserStore } from '@/store/global/user'; | ||||
import { useRequest } from '@/hooks/use-request'; | import { useRequest } from '@/hooks/use-request'; | ||||
import loginService from '@/request/service/login'; | |||||
import loginService from '@/request/service/auth'; | |||||
const Header = () => { | const Header = () => { | ||||
@@ -1,5 +1,11 @@ | |||||
export * from './user.ts' | export * from './user.ts' | ||||
export interface ResponseDTO<T>{ | |||||
code: number; | |||||
msg: string; | |||||
data: T | |||||
} | |||||
export interface PageData<T> { | export interface PageData<T> { | ||||
data: T[]; | data: T[]; | ||||
total: number; | total: number; | ||||
@@ -1,9 +1,11 @@ | |||||
export interface LoginDTO { | export interface LoginDTO { | ||||
userName: string; | |||||
captchaVerification: string; | |||||
username: string; | |||||
password: string; | password: string; | ||||
rememberMe: boolean; | |||||
tenantName: string; | |||||
} | } | ||||
export interface TokenDTO { | export interface TokenDTO { | ||||
userId: number, | userId: number, | ||||
accessToken: string; | accessToken: string; | ||||
@@ -55,33 +57,6 @@ export interface UserDTO { | |||||
idToString: string; | idToString: string; | ||||
} | } | ||||
export interface PopMenu { | |||||
id: number; | |||||
parentid: number; | |||||
homeid: number; | |||||
menuName: string; | |||||
parentMenuName: string; | |||||
pageUrl: string; | |||||
sort: number; | |||||
level: number; | |||||
} | |||||
export interface LoginRespDTO { | |||||
ack: number; | |||||
data: UserDTO; | |||||
msg: string; | |||||
pop: Array<PopMenu>; | |||||
} | |||||
// "id": 0, | |||||
// "parentId": 1024, | |||||
// "name": "芋道", | |||||
// "path": "post", | |||||
// "component": "system/post/index", | |||||
// "icon": "/menu/list", | |||||
// "visible": false, | |||||
// "keepAlive": false | |||||
export interface Menu { | export interface Menu { | ||||
id: string; | id: string; | ||||
parentId?: string; | parentId?: string; | ||||
@@ -43,7 +43,7 @@ const getBase64 = (file: RcFile): Promise<string> => | |||||
}); | }); | ||||
const CreateSampleAttr: React.FC<CreateSampleAttrProps> = (props) => { | |||||
const SampleAttrEditor: React.FC<CreateSampleAttrProps> = (props) => { | |||||
const { visible, onCancel, curRecord, onSave, editData } = props; | const { visible, onCancel, curRecord, onSave, editData } = props; | ||||
const [saveLoading, setSaveLoading] = useState(false); | const [saveLoading, setSaveLoading] = useState(false); | ||||
@@ -144,7 +144,7 @@ const CreateSampleAttr: React.FC<CreateSampleAttrProps> = (props) => { | |||||
okText="确认" | okText="确认" | ||||
cancelText="取消" | cancelText="取消" | ||||
> | > | ||||
<DeleteOutlined style={{ fontSize: '16px' }}/> | |||||
<DeleteOutlined style={{ fontSize: '16px' }} className='hover:(bg-[rgb(94,53,177)]'/> | |||||
</Popconfirm> | </Popconfirm> | ||||
} | } | ||||
> | > | ||||
@@ -207,4 +207,4 @@ const CreateSampleAttr: React.FC<CreateSampleAttrProps> = (props) => { | |||||
) | ) | ||||
} | } | ||||
export default CreateSampleAttr; | |||||
export default SampleAttrEditor; |
@@ -0,0 +1,165 @@ | |||||
import React, { useEffect, useMemo, useState } from 'react' | |||||
import { CloseOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'; | |||||
import { Drawer, Form, Input, Card, Space, Button, Upload, Popconfirm, Modal, FormListOperation, FormListFieldData } from 'antd' | |||||
import type { RcFile, UploadProps } from 'antd/es/upload'; | |||||
import type { UploadFile } from 'antd/es/upload/interface'; | |||||
const layout = { | |||||
labelCol: { span: 4, }, | |||||
wrapperCol: { span: 16 }, | |||||
bordered: false, | |||||
}; | |||||
// { | |||||
// "id": 412, | |||||
// "prototypeId": 89, | |||||
// "": 1991125, | |||||
// "maskImgId": 1991130, | |||||
// "imgName": "3.jpg", | |||||
// "imgType": 2, | |||||
// "imgWidth": 600.0, | |||||
// "imgHeight": 600.0, | |||||
// "imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A056.jpg", | |||||
// "maskImgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114413A061.png" | |||||
// } | |||||
export interface MaskPicture { | |||||
id: number; | |||||
prototypeId: number; | |||||
imgId: number; | |||||
maskImgId?: number; | |||||
imgName: string; | |||||
imgType: number; | |||||
imgWidth: number; | |||||
imgHeight: number; | |||||
imgUrl: string; | |||||
maskImgUrl?: string; | |||||
} | |||||
interface MaskPictureProps { | |||||
visible: boolean; | |||||
onCancel: (flag?: boolean) => void; | |||||
onSave: () => void; | |||||
dataSource?: MaskPicture[] | null; | |||||
} | |||||
const getBase64 = (file: RcFile): Promise<string> => | |||||
new Promise((resolve, reject) => { | |||||
const reader = new FileReader(); | |||||
reader.readAsDataURL(file); | |||||
reader.onload = () => resolve(reader.result as string); | |||||
reader.onerror = (error) => reject(error); | |||||
}); | |||||
const MaskPictureEditor: React.FC<MaskPictureProps> = (props) => { | |||||
const { visible, onCancel, onSave, dataSource } = props; | |||||
const [saveLoading, setSaveLoading] = useState(false); | |||||
const [previewOpen, setPreviewOpen] = useState(false); | |||||
const [previewImage, setPreviewImage] = useState(''); | |||||
const [previewTitle, setPreviewTitle] = useState(''); | |||||
const [form] = Form.useForm(); | |||||
useEffect(() => { | |||||
if (visible) { | |||||
setInitValue(); | |||||
} else { | |||||
form.resetFields(); | |||||
} | |||||
}, [visible]); | |||||
async function setInitValue() { | |||||
} | |||||
const save = async (values: any) => { | |||||
setSaveLoading(true); | |||||
setSaveLoading(false); | |||||
} | |||||
const handleCancel = () => setPreviewOpen(false); | |||||
const handlePreview = async (file: UploadFile) => { | |||||
if (!file.url && !file.preview) { | |||||
file.preview = await getBase64(file.originFileObj as RcFile); | |||||
} | |||||
setPreviewImage(file.url || (file.preview as string)); | |||||
setPreviewOpen(true); | |||||
setPreviewTitle(file.name || file.url!.substring(file.url!.lastIndexOf('/') + 1)); | |||||
}; | |||||
const uploadButton = (text: string) => ( | |||||
<div> | |||||
<PlusOutlined /> | |||||
<div style={{ marginTop: 8 }}>{text}</div> | |||||
</div> | |||||
); | |||||
const handleChange: UploadProps['onChange'] = (info) => { | |||||
debugger | |||||
} | |||||
const MaskPictureItem = (item: MaskPicture) => { | |||||
const mainPicture: UploadFile[] = [{ | |||||
uid: `${item.imgId}`, | |||||
name: item.imgUrl.split("/")[-1], | |||||
status: 'done', | |||||
url: item.imgUrl, | |||||
}] | |||||
const maskPicture: UploadFile[] = item.maskImgId ? [{ | |||||
uid: `${item.maskImgId}`, | |||||
name: item.maskImgUrl!!.split("/")[-1], | |||||
status: 'done', | |||||
url: item.maskImgUrl, | |||||
}] : [] | |||||
return ( | |||||
<div className='flex justify-start'> | |||||
<Upload | |||||
listType="picture-card" | |||||
fileList={mainPicture} | |||||
maxCount={1} | |||||
onPreview={handlePreview} | |||||
onChange={handleChange}> | |||||
{mainPicture.length === 0 && uploadButton("上传主图")} | |||||
</Upload> | |||||
<Upload | |||||
listType="picture-card" | |||||
fileList={maskPicture} | |||||
onPreview={handlePreview} | |||||
maxCount={1}> | |||||
{maskPicture.length === 0 && uploadButton("上传蒙版图")} | |||||
</Upload> | |||||
</div> | |||||
) | |||||
} | |||||
return ( | |||||
<> | |||||
<Drawer | |||||
open={visible} | |||||
title="蒙版图" | |||||
onClose={() => { onCancel() }} | |||||
extra={ | |||||
<Space> | |||||
<Button type="primary" size='middle' onClick={() => { save }}> | |||||
保存 | |||||
</Button> | |||||
</Space> | |||||
} | |||||
destroyOnClose | |||||
> | |||||
{dataSource?.map((item) => { | |||||
return MaskPictureItem(item) | |||||
})} | |||||
</Drawer> | |||||
<Modal open={previewOpen} title={previewTitle} footer={null} onCancel={handleCancel} zIndex={2000}> | |||||
<img alt="example" style={{ width: '100%' }} src={previewImage} /> | |||||
</Modal> | |||||
</> | |||||
) | |||||
} | |||||
export default MaskPictureEditor; |
@@ -4,8 +4,10 @@ import { t } from '@/utils/i18n'; | |||||
import { IconBuguang } from '@/assets/icons/buguang'; | import { IconBuguang } from '@/assets/icons/buguang'; | ||||
import React, { useState } from 'react'; | import React, { useState } from 'react'; | ||||
import type { TableRowSelection } from 'antd/es/table/interface'; | import type { TableRowSelection } from 'antd/es/table/interface'; | ||||
import CreateSampleAttr from './attr-editor' | |||||
import type { SampleAttribute } from './attr-editor' | |||||
import SampleAttrEditor from './components/attr-editor' | |||||
import MaskPictureEditor from './components/mask-picture-editor'; | |||||
import type { SampleAttribute } from './components/attr-editor' | |||||
import type { MaskPicture } from './components/mask-picture-editor'; | |||||
interface DataType { | interface DataType { | ||||
id: number; | id: number; | ||||
@@ -50,7 +52,7 @@ const TablePage: React.FC = () => { | |||||
key: 'prototypeName', | key: 'prototypeName', | ||||
}, | }, | ||||
{ | { | ||||
title:'类目', | |||||
title: '类目', | |||||
key: 'categoryName', | key: 'categoryName', | ||||
dataIndex: 'categoryName' | dataIndex: 'categoryName' | ||||
}, | }, | ||||
@@ -60,7 +62,7 @@ const TablePage: React.FC = () => { | |||||
key: 'createName', | key: 'createName', | ||||
}, | }, | ||||
{ | { | ||||
title:'创建时间', | |||||
title: '创建时间', | |||||
key: 'createTime', | key: 'createTime', | ||||
dataIndex: 'createTime' | dataIndex: 'createTime' | ||||
}, | }, | ||||
@@ -69,11 +71,13 @@ const TablePage: React.FC = () => { | |||||
key: 'action', | key: 'action', | ||||
render: (_, record) => ( | render: (_, record) => ( | ||||
<Space size="middle"> | <Space size="middle"> | ||||
<a>蒙版图 </a> | |||||
<a onClick={() => { | |||||
setMaskEditorVisible(true) | |||||
}}>蒙版图 </a> | |||||
<a | <a | ||||
onClick={() => { | onClick={() => { | ||||
// setEditData(record); | // setEditData(record); | ||||
setCreateVisible(true); | |||||
setAttrEditorVisible(true); | |||||
}}>属性设置</a> | }}>属性设置</a> | ||||
<a>编辑</a> | <a>编辑</a> | ||||
<a>删除</a> | <a>删除</a> | ||||
@@ -96,72 +100,123 @@ const TablePage: React.FC = () => { | |||||
dictDetails: [] | dictDetails: [] | ||||
}, | }, | ||||
{ | { | ||||
id: 76, | |||||
createTime: "2023-07-13 17:24:25", | |||||
spuCode: "2-47GEE7", | |||||
categoryId: 1264, | |||||
prototypeName: "kfc-test", | |||||
createId: 2, | |||||
categoryName: "男士T恤", | |||||
createName: "陈相荣✨", | |||||
oneImgUrl: "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A056.jpg", | |||||
dictDetails: [] | |||||
id: 76, | |||||
createTime: "2023-07-13 17:24:25", | |||||
spuCode: "2-47GEE7", | |||||
categoryId: 1264, | |||||
prototypeName: "kfc-test", | |||||
createId: 2, | |||||
categoryName: "男士T恤", | |||||
createName: "陈相荣✨", | |||||
oneImgUrl: "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A056.jpg", | |||||
dictDetails: [] | |||||
} | } | ||||
]; | ]; | ||||
const attrData: SampleAttribute[] = [ | const attrData: SampleAttribute[] = [ | ||||
{ | { | ||||
"id": 105, | |||||
"prototypeId": 88, | |||||
"attrName": "Color", | |||||
"isContainImg": 1, | |||||
"attrVals": [ | |||||
{ | |||||
"id": 273, | |||||
"attrId": 105, | |||||
"valName": "Pink", | |||||
"imgId": 1990260, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821104322A047.jpg" | |||||
}, | |||||
{ | |||||
"id": 274, | |||||
"attrId": 105, | |||||
"valName": "Black", | |||||
"imgId": 1990263, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821104322A050.jpg" | |||||
} | |||||
] | |||||
"id": 105, | |||||
"prototypeId": 88, | |||||
"attrName": "Color", | |||||
"isContainImg": 1, | |||||
"attrVals": [ | |||||
{ | |||||
"id": 273, | |||||
"attrId": 105, | |||||
"valName": "Pink", | |||||
"imgId": 1990260, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821104322A047.jpg" | |||||
}, | |||||
{ | |||||
"id": 274, | |||||
"attrId": 105, | |||||
"valName": "Black", | |||||
"imgId": 1990263, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821104322A050.jpg" | |||||
} | |||||
] | |||||
}, | }, | ||||
{ | { | ||||
"id": 106, | |||||
"prototypeId": 88, | |||||
"attrName": "Size", | |||||
"isContainImg": 2, | |||||
"attrVals": [ | |||||
{ | |||||
"id": 275, | |||||
"attrId": 106, | |||||
"valName": "XL" | |||||
}, | |||||
{ | |||||
"id": 276, | |||||
"attrId": 106, | |||||
"valName": "XXL" | |||||
} | |||||
] | |||||
"id": 106, | |||||
"prototypeId": 88, | |||||
"attrName": "Size", | |||||
"isContainImg": 2, | |||||
"attrVals": [ | |||||
{ | |||||
"id": 275, | |||||
"attrId": 106, | |||||
"valName": "XL" | |||||
}, | |||||
{ | |||||
"id": 276, | |||||
"attrId": 106, | |||||
"valName": "XXL" | |||||
} | |||||
] | |||||
} | } | ||||
] | |||||
] | |||||
const maskPictures: MaskPicture[] = [ | |||||
{ | |||||
"id": 412, | |||||
"prototypeId": 89, | |||||
"imgId": 1991125, | |||||
"maskImgId": 1991130, | |||||
"imgName": "3.jpg", | |||||
"imgType": 2, | |||||
"imgWidth": 600.0, | |||||
"imgHeight": 600.0, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A056.jpg", | |||||
"maskImgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114413A061.png" | |||||
}, | |||||
{ | |||||
"id": 413, | |||||
"prototypeId": 89, | |||||
"imgId": 1991122, | |||||
"maskImgId": 1991131, | |||||
"imgName": "1.jpg", | |||||
"imgType": 2, | |||||
"imgWidth": 600.0, | |||||
"imgHeight": 600.0, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A053.jpg", | |||||
"maskImgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114413A062.png" | |||||
}, | |||||
{ | |||||
"id": 414, | |||||
"prototypeId": 89, | |||||
"imgId": 1991127, | |||||
"imgName": "f60911abe38e82e9dd2aaa75f3292ed2.jpg", | |||||
"imgType": 2, | |||||
"imgWidth": 1000.0, | |||||
"imgHeight": 1000.0, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A058.jpg" | |||||
}, | |||||
{ | |||||
"id": 415, | |||||
"prototypeId": 89, | |||||
"imgId": 1991123, | |||||
"imgName": "d247c5ec0ef78f302b803c6bf00658a2.jpg", | |||||
"imgType": 2, | |||||
"imgWidth": 1000.0, | |||||
"imgHeight": 1000.0, | |||||
"imgUrl": "https://test.vogocm.com:9010/eshop/eshop_img/2023/8/21/20230821114346A054.jpg" | |||||
} | |||||
] | |||||
const [createVisible, setCreateVisible] = useState(false); | |||||
const [attrEditorVisible, setAttrEditorVisible] = useState(false); | |||||
const [maskEditorVisible, setMaskEditorVisible] = useState(false); | |||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | ||||
const cancelHandle = () => { | const cancelHandle = () => { | ||||
setCreateVisible(false); | |||||
setAttrEditorVisible(false); | |||||
}; | }; | ||||
const saveHandle = () => { | |||||
setCreateVisible(false); | |||||
const saveAttributeHandle = () => { | |||||
setAttrEditorVisible(false); | |||||
} | |||||
const saveMaskPictureHandle = () => { | |||||
setMaskEditorVisible(false); | |||||
} | } | ||||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => { | const onSelectChange = (newSelectedRowKeys: React.Key[]) => { | ||||
@@ -211,16 +266,22 @@ const TablePage: React.FC = () => { | |||||
return ( | return ( | ||||
<div> | <div> | ||||
<div className="dark:bg-[rgb(33,41,70)] rounded-md"> | <div className="dark:bg-[rgb(33,41,70)] rounded-md"> | ||||
<Table rowKey="id" rowSelection={rowSelection} scroll={{ x: true }} columns={columns} dataSource={data} className='bg-transparent' | |||||
<Table rowKey="id" rowSelection={rowSelection} scroll={{ x: true }} columns={columns} dataSource={data} className='bg-transparent' | |||||
pagination={{ position: ['bottomRight'] }} | pagination={{ position: ['bottomRight'] }} | ||||
/> | /> | ||||
</div> | </div> | ||||
<CreateSampleAttr | |||||
onSave={saveHandle} | |||||
<SampleAttrEditor | |||||
onSave={saveAttributeHandle} | |||||
onCancel={cancelHandle} | onCancel={cancelHandle} | ||||
visible={createVisible} | |||||
visible={attrEditorVisible} | |||||
curRecord={attrData} | curRecord={attrData} | ||||
editData={attrData}></CreateSampleAttr> | |||||
editData={attrData} /> | |||||
<MaskPictureEditor | |||||
onSave={saveMaskPictureHandle} | |||||
onCancel={()=>{ setMaskEditorVisible(false) }} | |||||
visible={maskEditorVisible} | |||||
dataSource={maskPictures} /> | |||||
</div> | </div> | ||||
); | ); | ||||
}; | }; | ||||
@@ -6,32 +6,31 @@ import { useNavigate } from 'react-router-dom'; | |||||
import { useRequest } from '@/hooks/use-request'; | import { useRequest } from '@/hooks/use-request'; | ||||
import { useUserStore } from '@/store/global/user'; | import { useUserStore } from '@/store/global/user'; | ||||
import { useGlobalStore } from '@/store/global'; | import { useGlobalStore } from '@/store/global'; | ||||
import loginService from '@/request/service/login'; | |||||
import loginService from '@/request/service/auth'; | |||||
import userService from '@/request/service/user'; | import userService from '@/request/service/user'; | ||||
import homeService from '@/request/service/home'; | |||||
import { LoginDTO } from '@/models'; | import { LoginDTO } from '@/models'; | ||||
import './index.css' | import './index.css' | ||||
const Login = () => { | const Login = () => { | ||||
const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
const { runAsync: getUserInfo } = useRequest(userService.getUserInfo, { manual: true }); | |||||
const { runAsync: getAnnouncement } = useRequest(homeService.getAnnouncement, { manual: true }); | |||||
const { runAsync: listMenus } = useRequest(userService.listMenus, { manual: true }); | |||||
const { runAsync: login, loading } = useRequest(loginService.login, { manual: true }); | const { runAsync: login, loading } = useRequest(loginService.login, { manual: true }); | ||||
const { runAsync: rerefshToken } = useRequest(loginService.rerefshToken, { manual: true }); | |||||
const { setCurrentUser } = useUserStore(); | const { setCurrentUser } = useUserStore(); | ||||
const { setCookie } = useGlobalStore(); | |||||
const { setToken, setRefreshToken } = useGlobalStore(); | |||||
const onFinish = async (values: LoginDTO) => { | const onFinish = async (values: LoginDTO) => { | ||||
const [loginError, data] = await login(values); | |||||
values.captchaVerification = "jKXK3cg440w2wbLQWBHOjDURNqT5sfMsQmkwMAEvzapLhQZh7YiMlEah/WhVXqygFmNO4SXEC4MzkjoRqgYK7A==" | |||||
values.rememberMe = true | |||||
const [loginError, {data}] = await login(values); | |||||
if (loginError) { | if (loginError) { | ||||
return; | return; | ||||
} | } | ||||
data.data.avatarUrl = 'https://test.vogocm.com:9010/eshop/eshop_img/2023/5/24/43853633d16749bfb291f81bebb73451_20230524150631A001.jpg'; | |||||
setCookie(data.msg); | |||||
setCurrentUser(data.data); | |||||
const [e, userInfo] = await getUserInfo(); | |||||
const [ _, announcementData ] = await getAnnouncement(); | |||||
console.log(announcementData) | |||||
console.log(userInfo) | |||||
// data.data.avatarUrl = 'https://test.vogocm.com:9010/eshop/eshop_img/2023/5/24/43853633d16749bfb291f81bebb73451_20230524150631A001.jpg'; | |||||
setRefreshToken(data.refreshToken); | |||||
setToken(data.accessToken); | |||||
const [ _, { data: menus } ] = await listMenus() | |||||
// const [ error, {data: tokenData}] = await rerefshToken(data.refreshToken) | |||||
debugger | |||||
navigate('/'); | navigate('/'); | ||||
}; | }; | ||||
@@ -51,12 +50,22 @@ const Login = () => { | |||||
<Form | <Form | ||||
name="super-admin" | name="super-admin" | ||||
className="login-form" | className="login-form" | ||||
initialValues={{ userName: 'admin', password: '1' }} | |||||
initialValues={{ username: 'admin', password: 'admin123', tenantName: '芋道源码' }} | |||||
onFinish={onFinish} | onFinish={onFinish} | ||||
size="large" | size="large" | ||||
> | > | ||||
<Form.Item | <Form.Item | ||||
name="userName" | |||||
name="tenantName" | |||||
rules={[{ required: true, message: '请输入用户名' }]} | |||||
> | |||||
<Input | |||||
prefix={<UserOutlined className="site-form-item-icon" />} | |||||
placeholder={t("RNISycbR" /* 账号 */)} | |||||
size="large" | |||||
/> | |||||
</Form.Item> | |||||
<Form.Item | |||||
name="username" | |||||
rules={[{ required: true, message: t("wVzXBuYs" /* 请输入账号 */) }]} | rules={[{ required: true, message: t("wVzXBuYs" /* 请输入账号 */) }]} | ||||
> | > | ||||
<Input | <Input | ||||
@@ -5,13 +5,16 @@ import axios, { | |||||
CreateAxiosDefaults, | CreateAxiosDefaults, | ||||
InternalAxiosRequestConfig, | InternalAxiosRequestConfig, | ||||
} from 'axios'; | } from 'axios'; | ||||
import {antdUtils} from '@/utils/antd'; | |||||
import {useGlobalStore} from '@/store/global'; | |||||
import { useGlobSetting } from '@/hooks/use-global-settings'; | import { useGlobSetting } from '@/hooks/use-global-settings'; | ||||
import loginService from '@/request/service/auth'; | |||||
import {antdUtils} from '@/utils/antd'; | |||||
import { ResponseDTO } from '@/models'; | |||||
const { apiUrl = '' } = useGlobSetting(); | const { apiUrl = '' } = useGlobSetting(); | ||||
// axios.defaults.withCredentials = true; | |||||
const refreshTokenUrl = '/api/auth/refresh/token'; | |||||
export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]>; | export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]>; | ||||
@@ -32,6 +35,7 @@ class Request { | |||||
private axiosInstance: AxiosInstance; | private axiosInstance: AxiosInstance; | ||||
private refreshTokenFlag = false; | |||||
private requestQueue: { | private requestQueue: { | ||||
resolve: any; | resolve: any; | ||||
config: any; | config: any; | ||||
@@ -48,7 +52,11 @@ class Request { | |||||
private async requestInterceptor( | private async requestInterceptor( | ||||
axiosConfig: InternalAxiosRequestConfig | axiosConfig: InternalAxiosRequestConfig | ||||
): Promise<any> { | ): Promise<any> { | ||||
if (this.requestingCount >= this.limit) { | |||||
if ([refreshTokenUrl].includes(axiosConfig.url || '')) { | |||||
return Promise.resolve(axiosConfig); | |||||
} | |||||
if (this.refreshTokenFlag || this.requestingCount >= this.limit) { | |||||
return new Promise((resolve) => { | return new Promise((resolve) => { | ||||
this.requestQueue.push({ | this.requestQueue.push({ | ||||
resolve, | resolve, | ||||
@@ -59,6 +67,12 @@ class Request { | |||||
} | } | ||||
this.requestingCount += 1; | this.requestingCount += 1; | ||||
const {token} = useGlobalStore.getState(); | |||||
if (token) { | |||||
axiosConfig.headers.Authorization = `Bearer ${token}`; | |||||
} | |||||
return Promise.resolve(axiosConfig); | return Promise.resolve(axiosConfig); | ||||
} | } | ||||
@@ -83,20 +97,47 @@ class Request { | |||||
resolve(await this.request(config)); | resolve(await this.request(config)); | ||||
} else if (type === 'reuqest') { | } else if (type === 'reuqest') { | ||||
this.requestingCount += 1; | this.requestingCount += 1; | ||||
//TODO: cookie | |||||
const {token} = useGlobalStore.getState(); | |||||
config.headers.Authorization = `Bearer ${token}`; | |||||
resolve(config); | resolve(config); | ||||
} | } | ||||
} | } | ||||
); | ); | ||||
} | } | ||||
private async refreshToken() { | |||||
const {refreshToken} = useGlobalStore.getState(); | |||||
if (!refreshToken) { | |||||
this.toLoginPage(); | |||||
} | |||||
const [error, {data}] = await loginService.rerefshToken(refreshToken); | |||||
if (error) { | |||||
this.toLoginPage(); | |||||
} | |||||
useGlobalStore.setState({ | |||||
refreshToken: data.refreshToken, | |||||
token: data.accessToken, | |||||
}); | |||||
this.refreshTokenFlag = false; | |||||
this.requestByQueue(); | |||||
} | |||||
private async responseSuccessInterceptor( | private async responseSuccessInterceptor( | ||||
response: AxiosResponse<any, any> | response: AxiosResponse<any, any> | ||||
): Promise<any> { | ): Promise<any> { | ||||
this.requestingCount -= 1; | |||||
if (this.requestQueue.length) { | |||||
this.requestByQueue(); | |||||
if (response.config.url !== refreshTokenUrl) { | |||||
this.requestingCount -= 1; | |||||
if (this.requestQueue.length) { | |||||
this.requestByQueue(); | |||||
} | |||||
} | } | ||||
return Promise.resolve([false, response.data, response]); | return Promise.resolve([false, response.data, response]); | ||||
} | } | ||||
@@ -107,6 +148,10 @@ class Request { | |||||
if (status === 401) { | if (status === 401) { | ||||
return new Promise((resolve) => { | return new Promise((resolve) => { | ||||
this.requestQueue.unshift({resolve, config, type: 'response'}); | this.requestQueue.unshift({resolve, config, type: 'response'}); | ||||
if (this.refreshTokenFlag) return; | |||||
this.refreshTokenFlag = true; | |||||
this.refreshToken(); | |||||
}); | }); | ||||
} else { | } else { | ||||
antdUtils.notification?.error({ | antdUtils.notification?.error({ | ||||
@@ -119,18 +164,20 @@ class Request { | |||||
private reset() { | private reset() { | ||||
this.requestQueue = []; | this.requestQueue = []; | ||||
this.refreshTokenFlag = false; | |||||
this.requestingCount = 0; | this.requestingCount = 0; | ||||
} | } | ||||
private toLoginPage() { | private toLoginPage() { | ||||
this.reset(); | this.reset(); | ||||
// router.navigate('/user/login'); | |||||
} | } | ||||
request<T, D = any>(config: AxiosRequestConfig<D>): Response<T> { | |||||
request<T, D = any>(config: AxiosRequestConfig<D>): Response<ResponseDTO<T>> { | |||||
return this.axiosInstance(config); | return this.axiosInstance(config); | ||||
} | } | ||||
get<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> { | |||||
get<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<ResponseDTO<T>> { | |||||
return this.axiosInstance.get(url, config); | return this.axiosInstance.get(url, config); | ||||
} | } | ||||
@@ -138,7 +185,7 @@ class Request { | |||||
url: string, | url: string, | ||||
data?: D, | data?: D, | ||||
config?: AxiosRequestConfig<D> | config?: AxiosRequestConfig<D> | ||||
): Response<T> { | |||||
): Response<ResponseDTO<T>> { | |||||
return this.axiosInstance.post(url, data, config); | return this.axiosInstance.post(url, data, config); | ||||
} | } | ||||
@@ -146,15 +193,15 @@ class Request { | |||||
url: string, | url: string, | ||||
data?: D, | data?: D, | ||||
config?: AxiosRequestConfig<D> | config?: AxiosRequestConfig<D> | ||||
): Response<T> { | |||||
): Response<ResponseDTO<T>> { | |||||
return this.axiosInstance.put(url, data, config); | return this.axiosInstance.put(url, data, config); | ||||
} | } | ||||
delete<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<T> { | |||||
delete<T, D = any>(url: string, config?: AxiosRequestConfig<D>): Response<ResponseDTO<T>> { | |||||
return this.axiosInstance.delete(url, config); | return this.axiosInstance.delete(url, config); | ||||
} | } | ||||
} | } | ||||
const request = new Request({timeout: 60 * 1000 * 5, baseURL: apiUrl}); | |||||
const request = new Request({timeout: 60 * 1000 * 5, baseURL: apiUrl}); | |||||
export default request; | export default request; |
@@ -0,0 +1,16 @@ | |||||
import request from '@/request'; | |||||
import { LoginDTO, TokenDTO } from '@/models' | |||||
export default { | |||||
// 登录 | |||||
login: (loginDTO: LoginDTO) => { | |||||
return request.post<TokenDTO>('/admin-api/system/auth/login', loginDTO); | |||||
}, | |||||
logout: () => { | |||||
return request.post<any>('/app-api/member/auth/logout'); | |||||
}, | |||||
rerefshToken: (refreshToken: string) => { | |||||
return request.post<TokenDTO>('/app-api/member/auth/refresh-token', { refreshToken }); | |||||
} | |||||
}; |
@@ -1,15 +0,0 @@ | |||||
import request from '@/request'; | |||||
import { LoginDTO, LoginRespDTO } from '@/models' | |||||
const loginService = { | |||||
// 登录 | |||||
login: (loginDTO: LoginDTO) => { | |||||
return request.post<LoginRespDTO>('/api/login', loginDTO, { withCredentials: false }); | |||||
}, | |||||
logout: () => { | |||||
return request.get<any>('/api/logout'); | |||||
} | |||||
}; | |||||
export default loginService; |
@@ -1,8 +1,8 @@ | |||||
import request from '@/request'; | import request from '@/request'; | ||||
import { LoginRespDTO } from '@/models'; | |||||
import { Menu } from '@/models'; | |||||
export default { | export default { | ||||
getUserInfo: () => { | |||||
return request.get<LoginRespDTO>('/api/userinfo/getUserInfo'); | |||||
listMenus: () => { | |||||
return request.get<Menu>('/admin-api/system/auth/list-menus'); | |||||
} | } | ||||
}; | }; |
@@ -5,14 +5,16 @@ interface State { | |||||
darkMode: boolean; | darkMode: boolean; | ||||
collapsed: boolean; | collapsed: boolean; | ||||
lang: string; | lang: string; | ||||
cookie: string; | |||||
token: string; | |||||
refreshToken: string; | |||||
} | } | ||||
interface Action { | interface Action { | ||||
setDarkMode: (darkMode: State['darkMode']) => void; | setDarkMode: (darkMode: State['darkMode']) => void; | ||||
setCollapsed: (collapsed: State['collapsed']) => void; | setCollapsed: (collapsed: State['collapsed']) => void; | ||||
setLang: (lang: State['lang']) => void; | setLang: (lang: State['lang']) => void; | ||||
setCookie: (cookie: State['cookie']) => void; | |||||
setToken: (token: State['token']) => void; | |||||
setRefreshToken: (refreshToken: State['refreshToken']) => void; | |||||
} | } | ||||
export const useGlobalStore = create<State & Action>()( | export const useGlobalStore = create<State & Action>()( | ||||
@@ -22,7 +24,8 @@ export const useGlobalStore = create<State & Action>()( | |||||
darkMode: false, | darkMode: false, | ||||
collapsed: false, | collapsed: false, | ||||
lang: 'zh', | lang: 'zh', | ||||
cookie: '', | |||||
token: '', | |||||
refreshToken: '', | |||||
setDarkMode: (darkMode: State['darkMode']) => set({ | setDarkMode: (darkMode: State['darkMode']) => set({ | ||||
darkMode, | darkMode, | ||||
}), | }), | ||||
@@ -32,8 +35,11 @@ export const useGlobalStore = create<State & Action>()( | |||||
setLang: (lang: State['lang']) => set({ | setLang: (lang: State['lang']) => set({ | ||||
lang, | lang, | ||||
}), | }), | ||||
setCookie: (cookie: State['cookie']) => set({ | |||||
cookie, | |||||
setToken: (token: State['token']) => set({ | |||||
token, | |||||
}), | |||||
setRefreshToken: (refreshToken: State['refreshToken']) => set({ | |||||
refreshToken, | |||||
}), | }), | ||||
}; | }; | ||||
}, | }, | ||||
@@ -16,12 +16,11 @@ export function getStorageShortName() { | |||||
export function getAppEnvConfig() { | export function getAppEnvConfig() { | ||||
const ENV_NAME = getConfigFileName(import.meta.env); | const ENV_NAME = getConfigFileName(import.meta.env); | ||||
console.log(import.meta.env) | |||||
const ENV = (import.meta.env.DEV | const ENV = (import.meta.env.DEV | ||||
? // Get the global configuration (the configuration will be extracted independently when packaging) | ? // Get the global configuration (the configuration will be extracted independently when packaging) | ||||
(import.meta.env as unknown as GlobEnvConfig) | (import.meta.env as unknown as GlobEnvConfig) | ||||
: window[ENV_NAME as any]) as unknown as GlobEnvConfig; | : window[ENV_NAME as any]) as unknown as GlobEnvConfig; | ||||
const { | const { | ||||
VITE_GLOB_APP_TITLE, | VITE_GLOB_APP_TITLE, | ||||
VITE_GLOB_API_URL, | VITE_GLOB_API_URL, | ||||
@@ -29,7 +28,6 @@ export function getAppEnvConfig() { | |||||
VITE_GLOB_API_URL_PREFIX, | VITE_GLOB_API_URL_PREFIX, | ||||
VITE_GLOB_UPLOAD_URL, | VITE_GLOB_UPLOAD_URL, | ||||
} = ENV; | } = ENV; | ||||
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { | if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { | ||||
warn( | warn( | ||||
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`, | `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`, | ||||
@@ -9,6 +9,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => { | |||||
const root = process.cwd(); | const root = process.cwd(); | ||||
const env = loadEnv(mode, root); | const env = loadEnv(mode, root); | ||||
console.log(env) | |||||
const viteEnv = wrapperEnv(env); | const viteEnv = wrapperEnv(env); | ||||
const { VITE_PORT } = viteEnv; | const { VITE_PORT } = viteEnv; | ||||