@@ -20,7 +20,6 @@ export function isReportMode(): boolean { | |||
// Read all environment variable configuration files to process.env | |||
export function wrapperEnv(envConf: Recordable): ViteEnv { | |||
const ret: any = {}; | |||
for (const envName of Object.keys(envConf)) { | |||
let realName = envConf[envName].replace(/\\n/g, '\n'); | |||
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 { useUserStore } from '@/store/global/user'; | |||
import { useRequest } from '@/hooks/use-request'; | |||
import loginService from '@/request/service/login'; | |||
import loginService from '@/request/service/auth'; | |||
const Header = () => { | |||
@@ -1,5 +1,11 @@ | |||
export * from './user.ts' | |||
export interface ResponseDTO<T>{ | |||
code: number; | |||
msg: string; | |||
data: T | |||
} | |||
export interface PageData<T> { | |||
data: T[]; | |||
total: number; | |||
@@ -1,9 +1,11 @@ | |||
export interface LoginDTO { | |||
userName: string; | |||
captchaVerification: string; | |||
username: string; | |||
password: string; | |||
rememberMe: boolean; | |||
tenantName: string; | |||
} | |||
export interface TokenDTO { | |||
userId: number, | |||
accessToken: string; | |||
@@ -55,33 +57,6 @@ export interface UserDTO { | |||
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 { | |||
id: 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 [saveLoading, setSaveLoading] = useState(false); | |||
@@ -144,7 +144,7 @@ const CreateSampleAttr: React.FC<CreateSampleAttrProps> = (props) => { | |||
okText="确认" | |||
cancelText="取消" | |||
> | |||
<DeleteOutlined style={{ fontSize: '16px' }}/> | |||
<DeleteOutlined style={{ fontSize: '16px' }} className='hover:(bg-[rgb(94,53,177)]'/> | |||
</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 React, { useState } from 'react'; | |||
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 { | |||
id: number; | |||
@@ -50,7 +52,7 @@ const TablePage: React.FC = () => { | |||
key: 'prototypeName', | |||
}, | |||
{ | |||
title:'类目', | |||
title: '类目', | |||
key: 'categoryName', | |||
dataIndex: 'categoryName' | |||
}, | |||
@@ -60,7 +62,7 @@ const TablePage: React.FC = () => { | |||
key: 'createName', | |||
}, | |||
{ | |||
title:'创建时间', | |||
title: '创建时间', | |||
key: 'createTime', | |||
dataIndex: 'createTime' | |||
}, | |||
@@ -69,11 +71,13 @@ const TablePage: React.FC = () => { | |||
key: 'action', | |||
render: (_, record) => ( | |||
<Space size="middle"> | |||
<a>蒙版图 </a> | |||
<a onClick={() => { | |||
setMaskEditorVisible(true) | |||
}}>蒙版图 </a> | |||
<a | |||
onClick={() => { | |||
// setEditData(record); | |||
setCreateVisible(true); | |||
setAttrEditorVisible(true); | |||
}}>属性设置</a> | |||
<a>编辑</a> | |||
<a>删除</a> | |||
@@ -96,72 +100,123 @@ const TablePage: React.FC = () => { | |||
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[] = [ | |||
{ | |||
"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 cancelHandle = () => { | |||
setCreateVisible(false); | |||
setAttrEditorVisible(false); | |||
}; | |||
const saveHandle = () => { | |||
setCreateVisible(false); | |||
const saveAttributeHandle = () => { | |||
setAttrEditorVisible(false); | |||
} | |||
const saveMaskPictureHandle = () => { | |||
setMaskEditorVisible(false); | |||
} | |||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => { | |||
@@ -211,16 +266,22 @@ const TablePage: React.FC = () => { | |||
return ( | |||
<div> | |||
<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'] }} | |||
/> | |||
</div> | |||
<CreateSampleAttr | |||
onSave={saveHandle} | |||
<SampleAttrEditor | |||
onSave={saveAttributeHandle} | |||
onCancel={cancelHandle} | |||
visible={createVisible} | |||
visible={attrEditorVisible} | |||
curRecord={attrData} | |||
editData={attrData}></CreateSampleAttr> | |||
editData={attrData} /> | |||
<MaskPictureEditor | |||
onSave={saveMaskPictureHandle} | |||
onCancel={()=>{ setMaskEditorVisible(false) }} | |||
visible={maskEditorVisible} | |||
dataSource={maskPictures} /> | |||
</div> | |||
); | |||
}; | |||
@@ -6,32 +6,31 @@ import { useNavigate } from 'react-router-dom'; | |||
import { useRequest } from '@/hooks/use-request'; | |||
import { useUserStore } from '@/store/global/user'; | |||
import { useGlobalStore } from '@/store/global'; | |||
import loginService from '@/request/service/login'; | |||
import loginService from '@/request/service/auth'; | |||
import userService from '@/request/service/user'; | |||
import homeService from '@/request/service/home'; | |||
import { LoginDTO } from '@/models'; | |||
import './index.css' | |||
const Login = () => { | |||
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: rerefshToken } = useRequest(loginService.rerefshToken, { manual: true }); | |||
const { setCurrentUser } = useUserStore(); | |||
const { setCookie } = useGlobalStore(); | |||
const { setToken, setRefreshToken } = useGlobalStore(); | |||
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) { | |||
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('/'); | |||
}; | |||
@@ -51,12 +50,22 @@ const Login = () => { | |||
<Form | |||
name="super-admin" | |||
className="login-form" | |||
initialValues={{ userName: 'admin', password: '1' }} | |||
initialValues={{ username: 'admin', password: 'admin123', tenantName: '芋道源码' }} | |||
onFinish={onFinish} | |||
size="large" | |||
> | |||
<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" /* 请输入账号 */) }]} | |||
> | |||
<Input | |||
@@ -5,13 +5,16 @@ import axios, { | |||
CreateAxiosDefaults, | |||
InternalAxiosRequestConfig, | |||
} from 'axios'; | |||
import {antdUtils} from '@/utils/antd'; | |||
import {useGlobalStore} from '@/store/global'; | |||
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(); | |||
// axios.defaults.withCredentials = true; | |||
const refreshTokenUrl = '/api/auth/refresh/token'; | |||
export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]>; | |||
@@ -32,6 +35,7 @@ class Request { | |||
private axiosInstance: AxiosInstance; | |||
private refreshTokenFlag = false; | |||
private requestQueue: { | |||
resolve: any; | |||
config: any; | |||
@@ -48,7 +52,11 @@ class Request { | |||
private async requestInterceptor( | |||
axiosConfig: InternalAxiosRequestConfig | |||
): 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) => { | |||
this.requestQueue.push({ | |||
resolve, | |||
@@ -59,6 +67,12 @@ class Request { | |||
} | |||
this.requestingCount += 1; | |||
const {token} = useGlobalStore.getState(); | |||
if (token) { | |||
axiosConfig.headers.Authorization = `Bearer ${token}`; | |||
} | |||
return Promise.resolve(axiosConfig); | |||
} | |||
@@ -83,20 +97,47 @@ class Request { | |||
resolve(await this.request(config)); | |||
} else if (type === 'reuqest') { | |||
this.requestingCount += 1; | |||
//TODO: cookie | |||
const {token} = useGlobalStore.getState(); | |||
config.headers.Authorization = `Bearer ${token}`; | |||
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( | |||
response: AxiosResponse<any, 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]); | |||
} | |||
@@ -107,6 +148,10 @@ class Request { | |||
if (status === 401) { | |||
return new Promise((resolve) => { | |||
this.requestQueue.unshift({resolve, config, type: 'response'}); | |||
if (this.refreshTokenFlag) return; | |||
this.refreshTokenFlag = true; | |||
this.refreshToken(); | |||
}); | |||
} else { | |||
antdUtils.notification?.error({ | |||
@@ -119,18 +164,20 @@ class Request { | |||
private reset() { | |||
this.requestQueue = []; | |||
this.refreshTokenFlag = false; | |||
this.requestingCount = 0; | |||
} | |||
private toLoginPage() { | |||
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); | |||
} | |||
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); | |||
} | |||
@@ -138,7 +185,7 @@ class Request { | |||
url: string, | |||
data?: D, | |||
config?: AxiosRequestConfig<D> | |||
): Response<T> { | |||
): Response<ResponseDTO<T>> { | |||
return this.axiosInstance.post(url, data, config); | |||
} | |||
@@ -146,15 +193,15 @@ class Request { | |||
url: string, | |||
data?: D, | |||
config?: AxiosRequestConfig<D> | |||
): Response<T> { | |||
): Response<ResponseDTO<T>> { | |||
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); | |||
} | |||
} | |||
const request = new Request({timeout: 60 * 1000 * 5, baseURL: apiUrl}); | |||
const request = new Request({timeout: 60 * 1000 * 5, baseURL: apiUrl}); | |||
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 { LoginRespDTO } from '@/models'; | |||
import { Menu } from '@/models'; | |||
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; | |||
collapsed: boolean; | |||
lang: string; | |||
cookie: string; | |||
token: string; | |||
refreshToken: string; | |||
} | |||
interface Action { | |||
setDarkMode: (darkMode: State['darkMode']) => void; | |||
setCollapsed: (collapsed: State['collapsed']) => 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>()( | |||
@@ -22,7 +24,8 @@ export const useGlobalStore = create<State & Action>()( | |||
darkMode: false, | |||
collapsed: false, | |||
lang: 'zh', | |||
cookie: '', | |||
token: '', | |||
refreshToken: '', | |||
setDarkMode: (darkMode: State['darkMode']) => set({ | |||
darkMode, | |||
}), | |||
@@ -32,8 +35,11 @@ export const useGlobalStore = create<State & Action>()( | |||
setLang: (lang: State['lang']) => set({ | |||
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() { | |||
const ENV_NAME = getConfigFileName(import.meta.env); | |||
console.log(import.meta.env) | |||
const ENV = (import.meta.env.DEV | |||
? // Get the global configuration (the configuration will be extracted independently when packaging) | |||
(import.meta.env as unknown as GlobEnvConfig) | |||
: window[ENV_NAME as any]) as unknown as GlobEnvConfig; | |||
const { | |||
VITE_GLOB_APP_TITLE, | |||
VITE_GLOB_API_URL, | |||
@@ -29,7 +28,6 @@ export function getAppEnvConfig() { | |||
VITE_GLOB_API_URL_PREFIX, | |||
VITE_GLOB_UPLOAD_URL, | |||
} = ENV; | |||
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) { | |||
warn( | |||
`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 env = loadEnv(mode, root); | |||
console.log(env) | |||
const viteEnv = wrapperEnv(env); | |||
const { VITE_PORT } = viteEnv; | |||