bug修复
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
This commit is contained in:
@@ -12,13 +12,18 @@
|
|||||||
*/
|
*/
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
|
// 原来是 redirect: '/account/center',改为默认渲染第三方过渡页
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/account/center',
|
component: './ThirdPartyRedirect',
|
||||||
|
// 不使用全局 layout,避免 layout 中的鉴权/重定向影响该过渡页
|
||||||
|
layout: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
// 原来是 redirect: '/account/center',改为默认渲染第三方过渡页
|
||||||
|
path: '/login-tow',
|
||||||
|
component: './ThirdPartyRedirect',
|
||||||
|
// 单独允许直接访问第三方过渡页
|
||||||
layout: false,
|
layout: false,
|
||||||
component: './404',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/user',
|
path: '/user',
|
||||||
@@ -162,6 +167,17 @@ export default [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'company',
|
||||||
|
path: '/company',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: '企业资质审核详情',
|
||||||
|
path: '/company/qualification-review/detail/:id',
|
||||||
|
component: './Company/QualificationReview/detail',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'jobfair',
|
name: 'jobfair',
|
||||||
path: '/jobfair',
|
path: '/jobfair',
|
||||||
@@ -183,5 +199,9 @@ export default [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
layout: false,
|
||||||
|
component: './404',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
BIN
shihezi.zip
BIN
shihezi.zip
Binary file not shown.
@@ -21,6 +21,7 @@ export function setSessionToken(
|
|||||||
access_token: string | undefined,
|
access_token: string | undefined,
|
||||||
refresh_token: string | undefined,
|
refresh_token: string | undefined,
|
||||||
expireTime: number,
|
expireTime: number,
|
||||||
|
lcToken: string | undefined,
|
||||||
): void{
|
): void{
|
||||||
if (access_token) {
|
if (access_token) {
|
||||||
localStorage.setItem('access_token', access_token);
|
localStorage.setItem('access_token', access_token);
|
||||||
@@ -32,6 +33,11 @@ export function setSessionToken(
|
|||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('refresh_token');
|
localStorage.removeItem('refresh_token');
|
||||||
}
|
}
|
||||||
|
if (lcToken) {
|
||||||
|
localStorage.setItem('lcToken', lcToken);
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('lcToken');
|
||||||
|
}
|
||||||
localStorage.setItem('expireTime', `${expireTime}`);
|
localStorage.setItem('expireTime', `${expireTime}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
src/app.tsx
86
src/app.tsx
@@ -6,6 +6,10 @@ import { history } from '@umijs/max';
|
|||||||
import defaultSettings from '../config/defaultSettings';
|
import defaultSettings from '../config/defaultSettings';
|
||||||
import { errorConfig } from './requestErrorConfig';
|
import { errorConfig } from './requestErrorConfig';
|
||||||
import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
|
import { clearSessionToken, getAccessToken, getRefreshToken, getTokenExpireTime } from './access';
|
||||||
|
import {
|
||||||
|
exchangeThirdPartyCredential,
|
||||||
|
isThirdPartyTransitionPage,
|
||||||
|
} from './utils/thirdPartyLogin';
|
||||||
import {
|
import {
|
||||||
getRemoteMenu,
|
getRemoteMenu,
|
||||||
getRoutersInfo,
|
getRoutersInfo,
|
||||||
@@ -69,7 +73,17 @@ export async function getInitialState(): Promise<{
|
|||||||
};
|
};
|
||||||
// 如果不是登录页面,执行
|
// 如果不是登录页面,执行
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
if (location.pathname !== PageEnum.LOGIN) {
|
// 排除登录页面、过渡页面和求职者页面(考虑基础路径 /shihezi/)
|
||||||
|
const isTransitionPage = location.pathname === '/' ||
|
||||||
|
location.pathname === '/login-tow' ||
|
||||||
|
location.pathname === '/shihezi/' ||
|
||||||
|
location.pathname === '/shihezi/login-tow';
|
||||||
|
|
||||||
|
// 排除求职者相关页面
|
||||||
|
const isJobPortalPage = location.pathname.startsWith('/job-portal') ||
|
||||||
|
location.pathname.startsWith('/shihezi/job-portal');
|
||||||
|
|
||||||
|
if (location.pathname !== PageEnum.LOGIN && !isTransitionPage && !isJobPortalPage) {
|
||||||
const currentUser = await fetchUserInfo();
|
const currentUser = await fetchUserInfo();
|
||||||
return {
|
return {
|
||||||
fetchUserInfo,
|
fetchUserInfo,
|
||||||
@@ -91,9 +105,9 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
avatarProps: {
|
avatarProps: {
|
||||||
src: initialState?.currentUser?.avatar,
|
src: initialState?.currentUser?.avatar,
|
||||||
title: <AvatarName />,
|
title: <AvatarName />,
|
||||||
render: (_, avatarChildren) => {
|
render: (_, avatarChildren) => {
|
||||||
return <AvatarDropdown menu="True">{avatarChildren}</AvatarDropdown>;
|
return <AvatarDropdown menu>{avatarChildren}</AvatarDropdown>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
waterMarkProps: {
|
waterMarkProps: {
|
||||||
// content: initialState?.currentUser?.nickName,
|
// content: initialState?.currentUser?.nickName,
|
||||||
@@ -115,8 +129,17 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
footerRender: () => <Footer />,
|
footerRender: () => <Footer />,
|
||||||
onPageChange: () => {
|
onPageChange: () => {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
// 如果没有登录,重定向到 login
|
// 如果没有登录,重定向到 login,但排除过渡页面和求职者页面(考虑基础路径 /shihezi/)
|
||||||
if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) {
|
const isTransitionPage = location.pathname === '/' ||
|
||||||
|
location.pathname === '/login-tow' ||
|
||||||
|
location.pathname === '/shihezi/' ||
|
||||||
|
location.pathname === '/shihezi/login-tow';
|
||||||
|
|
||||||
|
// 排除求职者相关页面
|
||||||
|
const isJobPortalPage = location.pathname.startsWith('/job-portal') ||
|
||||||
|
location.pathname.startsWith('/shihezi/job-portal');
|
||||||
|
|
||||||
|
if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN && !isTransitionPage && !isJobPortalPage) {
|
||||||
history.push(PageEnum.LOGIN);
|
history.push(PageEnum.LOGIN);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -176,12 +199,23 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function onRouteChange({ clientRoutes, location }) {
|
export async function onRouteChange({ clientRoutes, location }: { clientRoutes: any; location: any }) {
|
||||||
const menus = getRemoteMenu();
|
const menus = getRemoteMenu();
|
||||||
// console.log('onRouteChange', clientRoutes, location, menus);
|
// console.log('onRouteChange', clientRoutes, location, menus);
|
||||||
if (menus === null && location.pathname !== PageEnum.LOGIN) {
|
|
||||||
console.log('refresh');
|
// 如果路由信息未加载,尝试重新获取路由信息
|
||||||
// history.go(0);
|
const token = getAccessToken();
|
||||||
|
if (menus === null && location.pathname !== PageEnum.LOGIN && token) {
|
||||||
|
console.log('检测到路由信息未加载,正在重新获取路由信息...');
|
||||||
|
|
||||||
|
// 重新获取路由信息,而不是刷新页面
|
||||||
|
try {
|
||||||
|
const routers = await getRoutersInfo();
|
||||||
|
setRemoteMenu(routers);
|
||||||
|
console.log('路由信息重新获取成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('重新获取路由信息失败:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,24 +223,40 @@ export async function onRouteChange({ clientRoutes, location }) {
|
|||||||
// console.log('patchRoutes', routes, routeComponents);
|
// console.log('patchRoutes', routes, routeComponents);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export async function patchClientRoutes({ routes }) {
|
export async function patchClientRoutes({ routes }: { routes: any }) {
|
||||||
// console.log('patchClientRoutes', routes);
|
// console.log('patchClientRoutes', routes);
|
||||||
patchRouteWithRemoteMenus(routes);
|
patchRouteWithRemoteMenus(routes);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function render(oldRender: () => void) {
|
export async function render(oldRender: () => void) {
|
||||||
console.log('render get routers', oldRender);
|
const { pathname, search } = window.location;
|
||||||
|
const params = new URLSearchParams(search);
|
||||||
|
const code = params.get('code');
|
||||||
|
const tokenParam = params.get('token');
|
||||||
|
|
||||||
|
// 过渡页带 code/token 时,先完成 SSO 换票再拉路由,避免旧 token 导致 getRouters 401
|
||||||
|
if (isThirdPartyTransitionPage(pathname) && (code || tokenParam)) {
|
||||||
|
clearSessionToken();
|
||||||
|
const ok = await exchangeThirdPartyCredential({ code, token: tokenParam });
|
||||||
|
if (!ok) {
|
||||||
|
oldRender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const token = getAccessToken();
|
const token = getAccessToken();
|
||||||
if (!token || token?.length === 0) {
|
if (!token || token.length === 0) {
|
||||||
oldRender();
|
oldRender();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await getRoutersInfo().then((res) => {
|
|
||||||
console.log('render get routers', 123);
|
|
||||||
|
|
||||||
setRemoteMenu(res);
|
try {
|
||||||
oldRender();
|
const routers = await getRoutersInfo();
|
||||||
});
|
setRemoteMenu(routers);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取路由失败:', error);
|
||||||
|
}
|
||||||
|
oldRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
164
src/pages/Company/List/detail.less
Normal file
164
src/pages/Company/List/detail.less
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
.company-detail {
|
||||||
|
&__header {
|
||||||
|
margin: -8px -8px 20px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, #f0f5ff 0%, #e6f4ff 50%, #f9f0ff 100%);
|
||||||
|
border: 1px solid #d6e4ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__industry {
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(0, 0, 0, 0.55);
|
||||||
|
padding: 2px 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.75);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__section-title {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-left: 10px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
width: 3px;
|
||||||
|
height: 14px;
|
||||||
|
margin-top: -7px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: #1677ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__desc {
|
||||||
|
:global {
|
||||||
|
.ant-descriptions-item-label {
|
||||||
|
width: 108px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
background: #fafafa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-descriptions-item-content {
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__intro {
|
||||||
|
padding: 14px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.75;
|
||||||
|
color: rgba(0, 0, 0, 0.75);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contacts {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contact-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: box-shadow 0.2s, border-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #d6e4ff;
|
||||||
|
box-shadow: 0 2px 8px rgba(22, 119, 255, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contact-avatar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1677ff;
|
||||||
|
background: #e6f4ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contact-body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contact-name {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(0, 0, 0, 0.88);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contact-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.anticon) {
|
||||||
|
color: rgba(0, 0, 0, 0.35);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
color: rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,26 @@ import {
|
|||||||
ProFormTextArea,
|
ProFormTextArea,
|
||||||
ProFormTreeSelect,
|
ProFormTreeSelect,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Form, Button, message, Modal, Descriptions, Space } from 'antd';
|
import { Form, Button, message, Modal, Descriptions, Space, Spin, Empty, Tag } from 'antd';
|
||||||
import { PlusOutlined, MinusCircleOutlined, DeleteOutlined } from '@ant-design/icons';
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
MinusCircleOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
IdcardOutlined,
|
||||||
|
PhoneOutlined,
|
||||||
|
UserOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
import DictTag, { DictValueEnumObj } from '@/components/DictTag';
|
import DictTag, { DictValueEnumObj } from '@/components/DictTag';
|
||||||
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||||
|
import './detail.less';
|
||||||
|
|
||||||
|
const renderDetailText = (value?: string | null) => {
|
||||||
|
const text = value?.toString().trim();
|
||||||
|
if (!text) {
|
||||||
|
return <span className="company-detail__empty">—</span>;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
};
|
||||||
export type ListFormProps = {
|
export type ListFormProps = {
|
||||||
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
||||||
onSubmit: (values: API.CompanyList.Company) => Promise<void>;
|
onSubmit: (values: API.CompanyList.Company) => Promise<void>;
|
||||||
@@ -189,52 +205,125 @@ export const CompanyDetailView = ({
|
|||||||
open,
|
open,
|
||||||
onCancel,
|
onCancel,
|
||||||
record,
|
record,
|
||||||
scaleEnum
|
scaleEnum,
|
||||||
|
loading,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
record?: API.CompanyList.Company;
|
record?: API.CompanyList.Company;
|
||||||
scaleEnum?: DictValueEnumObj;
|
scaleEnum?: DictValueEnumObj;
|
||||||
|
loading?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const contacts = record?.companyContactList?.filter(
|
||||||
|
(c) => c.contactPerson || c.contactPersonPhone || c.position,
|
||||||
|
);
|
||||||
|
|
||||||
|
const statusTag =
|
||||||
|
record?.status === 1 ? (
|
||||||
|
<Tag color="success">审核通过</Tag>
|
||||||
|
) : record?.status === 2 ? (
|
||||||
|
<Tag color="error">已驳回</Tag>
|
||||||
|
) : record?.status !== undefined ? (
|
||||||
|
<Tag>待审核</Tag>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="公司详情"
|
title="企业详情"
|
||||||
open={open}
|
open={open}
|
||||||
width={600}
|
width={720}
|
||||||
|
centered
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
footer={[
|
destroyOnClose
|
||||||
<Button key="back" onClick={onCancel}>
|
styles={{ body: { paddingTop: 16 } }}
|
||||||
关闭
|
footer={
|
||||||
</Button>,
|
<div className="company-detail__footer">
|
||||||
]}
|
<Button type="primary" onClick={onCancel}>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div style={{ padding: '20px' }}>
|
<Spin spinning={loading}>
|
||||||
<Descriptions column={1} bordered>
|
<div className="company-detail">
|
||||||
<Descriptions.Item label="单位名称">{record?.name}</Descriptions.Item>
|
<div className="company-detail__header">
|
||||||
<Descriptions.Item label="主要行业">{record?.industry}</Descriptions.Item>
|
<h3 className="company-detail__name">{renderDetailText(record?.name)}</h3>
|
||||||
<Descriptions.Item label="单位规模">
|
<div className="company-detail__meta">
|
||||||
<DictTag enums={scaleEnum} value={record?.scale} />
|
{record?.scale != null && record.scale !== '' && (
|
||||||
</Descriptions.Item>
|
<DictTag enums={scaleEnum} value={record.scale} />
|
||||||
<Descriptions.Item label="公司位置">{record?.location}</Descriptions.Item>
|
)}
|
||||||
<Descriptions.Item label="信用代码">{record?.code}</Descriptions.Item>
|
{statusTag}
|
||||||
<Descriptions.Item label="单位介绍">{record?.description}</Descriptions.Item>
|
{record?.industry ? (
|
||||||
<Descriptions.Item label="企业联系人">
|
<span className="company-detail__industry">{record.industry}</span>
|
||||||
{record?.companyContactList && record.companyContactList.length > 0 ? (
|
) : null}
|
||||||
<div>
|
</div>
|
||||||
{record.companyContactList.map((contact, index) => (
|
</div>
|
||||||
<div key={index} style={{ marginBottom: 8 }}>
|
|
||||||
<div><strong>联系人:</strong>{contact.contactPerson}</div>
|
<div className="company-detail__section">
|
||||||
<div><strong>联系电话:</strong>{contact.contactPersonPhone}</div>
|
<div className="company-detail__section-title">基本信息</div>
|
||||||
<div><strong>职务:</strong>{contact.position}</div>
|
<Descriptions
|
||||||
|
column={2}
|
||||||
|
bordered
|
||||||
|
size="middle"
|
||||||
|
className="company-detail__desc"
|
||||||
|
>
|
||||||
|
<Descriptions.Item label="公司位置" span={2}>
|
||||||
|
{renderDetailText(record?.location as string)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="信用代码" span={2}>
|
||||||
|
{renderDetailText(record?.code)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="company-detail__section">
|
||||||
|
<div className="company-detail__section-title">单位介绍</div>
|
||||||
|
{record?.description?.trim() ? (
|
||||||
|
<div className="company-detail__intro">{record.description}</div>
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description="暂无单位介绍"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="company-detail__section">
|
||||||
|
<div className="company-detail__section-title">企业联系人</div>
|
||||||
|
{contacts && contacts.length > 0 ? (
|
||||||
|
<div className="company-detail__contacts">
|
||||||
|
{contacts.map((contact, index) => (
|
||||||
|
<div key={index} className="company-detail__contact-card">
|
||||||
|
<div className="company-detail__contact-avatar">
|
||||||
|
<UserOutlined />
|
||||||
|
</div>
|
||||||
|
<div className="company-detail__contact-body">
|
||||||
|
<div className="company-detail__contact-name">
|
||||||
|
{renderDetailText(contact.contactPerson)}
|
||||||
|
</div>
|
||||||
|
<div className="company-detail__contact-row">
|
||||||
|
<PhoneOutlined />
|
||||||
|
<span>{renderDetailText(contact.contactPersonPhone)}</span>
|
||||||
|
</div>
|
||||||
|
{contact.position?.trim() ? (
|
||||||
|
<div className="company-detail__contact-row">
|
||||||
|
<IdcardOutlined />
|
||||||
|
<span>{contact.position}</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span>暂无联系人信息</span>
|
<Empty
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
description="暂无联系人信息"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Descriptions.Item>
|
</div>
|
||||||
</Descriptions>
|
</div>
|
||||||
</div>
|
</Spin>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,69 +1,29 @@
|
|||||||
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
import { FormattedMessage, useAccess } from '@umijs/max';
|
import { FormattedMessage, useAccess } from '@umijs/max';
|
||||||
import { Button, FormInstance, message, Modal, Descriptions } from 'antd';
|
import { Button, FormInstance, message, Modal } from 'antd';
|
||||||
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
import { DeleteOutlined, FormOutlined, PlusOutlined, EyeOutlined, AlignLeftOutlined, AuditOutlined } from '@ant-design/icons';
|
import { DeleteOutlined, FormOutlined, PlusOutlined, AlignLeftOutlined, AuditOutlined } from '@ant-design/icons';
|
||||||
import EditCompanyListRow from './edit';
|
import EditCompanyListRow, { CompanyDetailView } from './edit';
|
||||||
import {
|
import {
|
||||||
addCmsCompanyList,
|
addCmsCompanyList,
|
||||||
delCmsCompanyList,
|
delCmsCompanyList,
|
||||||
exportCmsCompanyList,
|
exportCmsCompanyList,
|
||||||
getCmsCompanyList,
|
getCmsCompanyList,
|
||||||
|
getCmsCompanyDetail,
|
||||||
|
parseCmsCompanyDetailResponse,
|
||||||
putCmsCompanyList,
|
putCmsCompanyList,
|
||||||
} from '@/services/company/list';
|
} from '@/services/company/list';
|
||||||
import { getCmsCompanyDetail } from '@/services/company/list';
|
|
||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import DictTag from '@/components/DictTag';
|
import DictTag from '@/components/DictTag';
|
||||||
import { postCompanyApproval } from '@/services/company/review';
|
import { postCompanyApproval } from '@/services/company/review';
|
||||||
|
|
||||||
// 详情查看组件
|
const loadCompanyDetail = async (record: API.CompanyList.Company) => {
|
||||||
const CompanyDetailModal = ({
|
const resp = await getCmsCompanyDetail(record.companyId);
|
||||||
visible,
|
const detail = parseCmsCompanyDetailResponse(resp);
|
||||||
onCancel,
|
if (!detail) {
|
||||||
record,
|
return { ...record };
|
||||||
scaleEnum
|
}
|
||||||
}: {
|
return { ...record, ...detail };
|
||||||
visible: boolean;
|
|
||||||
onCancel: () => void;
|
|
||||||
record?: API.CompanyList.Company;
|
|
||||||
scaleEnum: Record<string, any>;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title="公司详情"
|
|
||||||
open={visible}
|
|
||||||
width={600}
|
|
||||||
onCancel={onCancel}
|
|
||||||
footer={[
|
|
||||||
<Button key="back" onClick={onCancel}>
|
|
||||||
关闭
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Descriptions column={1} bordered>
|
|
||||||
<Descriptions.Item label="公司名称">{record?.name}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="公司行业">{record?.industry}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="公司规模">
|
|
||||||
<DictTag enums={scaleEnum} value={record?.scale} />
|
|
||||||
</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="公司位置">{record?.location}</Descriptions.Item>
|
|
||||||
<Descriptions.Item label="企业联系人">
|
|
||||||
{record?.companyContactList && record.companyContactList.length > 0 ? (
|
|
||||||
<div>
|
|
||||||
{record.companyContactList.map((contact, index) => (
|
|
||||||
<div key={index} style={{ marginBottom: 8 }}>
|
|
||||||
<div><strong>联系人:</strong>{contact.contactPerson}</div>
|
|
||||||
<div><strong>联系电话:</strong>{contact.contactPersonPhone}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<span>暂无联系人信息</span>
|
|
||||||
)}
|
|
||||||
</Descriptions.Item>
|
|
||||||
</Descriptions>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function ManagementList() {
|
function ManagementList() {
|
||||||
@@ -203,12 +163,12 @@ function ManagementList() {
|
|||||||
icon={<AlignLeftOutlined />}
|
icon={<AlignLeftOutlined />}
|
||||||
hidden={!access.hasPerms('area:business:List.view')}
|
hidden={!access.hasPerms('area:business:List.view')}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setCurrentRow(record);
|
setDetailData({ ...record });
|
||||||
|
setDetailVisible(true);
|
||||||
setDetailLoading(true);
|
setDetailLoading(true);
|
||||||
try {
|
try {
|
||||||
const resp = await getCmsCompanyDetail(record.companyId);
|
const merged = await loadCompanyDetail(record);
|
||||||
setDetailData(resp?.data);
|
setDetailData(merged);
|
||||||
setDetailVisible(true);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error('获取详情失败');
|
message.error('获取详情失败');
|
||||||
} finally {
|
} finally {
|
||||||
@@ -227,9 +187,8 @@ function ManagementList() {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const hide = message.loading('正在加载详情');
|
const hide = message.loading('正在加载详情');
|
||||||
try {
|
try {
|
||||||
const resp = await getCmsCompanyDetail(record.companyId);
|
const merged = await loadCompanyDetail(record);
|
||||||
const detail = resp?.data || {};
|
setCurrentRow(merged);
|
||||||
setCurrentRow({ ...record, ...detail });
|
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
message.error('获取详情失败');
|
message.error('获取详情失败');
|
||||||
@@ -354,11 +313,11 @@ function ManagementList() {
|
|||||||
scaleEnum={scaleEnum}
|
scaleEnum={scaleEnum}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CompanyDetailModal
|
<CompanyDetailView
|
||||||
visible={detailVisible}
|
open={detailVisible}
|
||||||
|
loading={detailLoading}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setDetailVisible(false);
|
setDetailVisible(false);
|
||||||
setCurrentRow(undefined);
|
|
||||||
setDetailData(undefined);
|
setDetailData(undefined);
|
||||||
}}
|
}}
|
||||||
record={detailData}
|
record={detailData}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Card, Descriptions, Spin, Typography } from 'antd';
|
import { Button, Card, Descriptions, Spin, Typography } from 'antd';
|
||||||
import { useParams } from 'umi';
|
import { history, useParams } from '@umijs/max';
|
||||||
import { getCompanyDetail } from '@/services/company/review';
|
|
||||||
|
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
@@ -57,6 +56,11 @@ export default function CompanyDetail() {
|
|||||||
return (
|
return (
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<Card className="company-detail-card">
|
<Card className="company-detail-card">
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Button type="link" onClick={() => history.back()}>
|
||||||
|
返回列表
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<Title level={4}>公司基本信息</Title>
|
<Title level={4}>公司基本信息</Title>
|
||||||
<Descriptions column={1} bordered>
|
<Descriptions column={1} bordered>
|
||||||
<Descriptions.Item label="公司名称">{companyDetail.name}</Descriptions.Item>
|
<Descriptions.Item label="公司名称">{companyDetail.name}</Descriptions.Item>
|
||||||
|
|||||||
202
src/pages/ThirdPartyRedirect.tsx
Normal file
202
src/pages/ThirdPartyRedirect.tsx
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Spin, message } from 'antd';
|
||||||
|
import { history, useLocation, useModel } from '@umijs/max';
|
||||||
|
import { getAccessToken } from '@/access';
|
||||||
|
import { getRoutersInfo, setRemoteMenu } from '@/services/session';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
|
|
||||||
|
/** 过渡页文案:避免「登录」表述,强调无缝进入 */
|
||||||
|
const STATUS_TEXT = {
|
||||||
|
entering: '正在进入系统…',
|
||||||
|
verifying: '正在核验访问信息…',
|
||||||
|
preparing: '正在准备页面…',
|
||||||
|
loadingProfile: '正在加载您的资料…',
|
||||||
|
noCredential: '未检测到有效访问凭证,请从原系统入口进入',
|
||||||
|
credentialInvalid: '访问凭证已失效,请从原系统重新进入',
|
||||||
|
profileFailed: '暂时无法加载您的信息,请稍后重试',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
const HINT_TEXT = '请稍候,页面即将呈现';
|
||||||
|
|
||||||
|
const pageStyle: React.CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
minHeight: '100vh',
|
||||||
|
padding: '24px',
|
||||||
|
background: 'linear-gradient(180deg, #f0f5ff 0%, #f5f7fa 48%, #fafafa 100%)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const cardStyle: React.CSSProperties = {
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: 420,
|
||||||
|
padding: '48px 40px 40px',
|
||||||
|
borderRadius: 16,
|
||||||
|
background: '#fff',
|
||||||
|
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.06)',
|
||||||
|
border: '1px solid rgba(0, 0, 0, 0.04)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleStyle: React.CSSProperties = {
|
||||||
|
marginTop: 28,
|
||||||
|
marginBottom: 0,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'rgba(0, 0, 0, 0.88)',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusStyle: React.CSSProperties = {
|
||||||
|
marginTop: 12,
|
||||||
|
marginBottom: 0,
|
||||||
|
fontSize: 15,
|
||||||
|
color: 'rgba(0, 0, 0, 0.65)',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
};
|
||||||
|
|
||||||
|
const hintStyle: React.CSSProperties = {
|
||||||
|
marginTop: 8,
|
||||||
|
fontSize: 14,
|
||||||
|
color: 'rgba(0, 0, 0, 0.45)',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ThirdPartyRedirect() {
|
||||||
|
const location = useLocation();
|
||||||
|
const { initialState, setInitialState } = useModel('@@initialState');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [status, setStatus] = useState<string>(STATUS_TEXT.entering);
|
||||||
|
const [isError, setIsError] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleLoginRedirect();
|
||||||
|
}, [location.search]);
|
||||||
|
|
||||||
|
const fetchUserInfo = async () => {
|
||||||
|
try {
|
||||||
|
const userInfo = await initialState?.fetchUserInfo?.();
|
||||||
|
if (userInfo) {
|
||||||
|
console.log('userInfo', userInfo);
|
||||||
|
console.log('user roles:', userInfo?.roles);
|
||||||
|
console.log('first role:', userInfo?.roles?.[0]);
|
||||||
|
flushSync(() => {
|
||||||
|
setInitialState((s) => ({
|
||||||
|
...s,
|
||||||
|
currentUser: userInfo,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
if (Number(userInfo?.userType) === 1101) {
|
||||||
|
Number(userInfo?.userType)
|
||||||
|
Number(userInfo?.userType)
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const pageName = params.get('pageName');
|
||||||
|
console.log('userType value:', userInfo?.userType, 'type:', typeof userInfo?.userType);
|
||||||
|
if (!pageName) {
|
||||||
|
history.replace(`/job-portal`);
|
||||||
|
}
|
||||||
|
if (pageName === 'message') {
|
||||||
|
history.replace(`/job-portal/${pageName}`);
|
||||||
|
}
|
||||||
|
if (pageName === 'jobDetail') {
|
||||||
|
history.replace(`/job-portal/detail/${params.get('jobId')}`);
|
||||||
|
}
|
||||||
|
if (pageName === 'personal-center') {
|
||||||
|
history.replace(`/job-portal/${pageName}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const menus = await getRoutersInfo();
|
||||||
|
console.log('userType value:', userInfo?.userType, 'type:', typeof userInfo?.userType);
|
||||||
|
if (menus && menus.length > 0 && Number(userInfo?.userType) !== 1101) {
|
||||||
|
setRemoteMenu(menus);
|
||||||
|
window.location.replace('http://39.98.44.136:6024/shihezi/management/management/list/index')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
message.error('暂时无法加载您的信息');
|
||||||
|
setIsError(true);
|
||||||
|
setStatus(STATUS_TEXT.profileFailed);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setIsError(true);
|
||||||
|
setStatus(STATUS_TEXT.profileFailed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoginRedirect = async () => {
|
||||||
|
try {
|
||||||
|
setIsError(false);
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
const code = params.get('code');
|
||||||
|
const token = params.get('token');
|
||||||
|
const pageName = params.get('pageName');
|
||||||
|
|
||||||
|
if (!code && !token && !pageName) {
|
||||||
|
setIsError(true);
|
||||||
|
setStatus(STATUS_TEXT.noCredential);
|
||||||
|
}
|
||||||
|
if (code || token) {
|
||||||
|
const sessionToken = getAccessToken();
|
||||||
|
if (!sessionToken) {
|
||||||
|
setIsError(true);
|
||||||
|
setStatus(STATUS_TEXT.credentialInvalid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setStatus(STATUS_TEXT.loadingProfile);
|
||||||
|
await fetchUserInfo();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
if (!pageName) {
|
||||||
|
history.replace(`/job-portal`);
|
||||||
|
}
|
||||||
|
if (pageName === 'message') {
|
||||||
|
history.replace(`/job-portal/${pageName}`);
|
||||||
|
}
|
||||||
|
if (pageName === 'jobDetail') {
|
||||||
|
history.replace(`/job-portal/detail/${params.get('jobId')}`);
|
||||||
|
}
|
||||||
|
if (pageName === 'personal-center') {
|
||||||
|
history.replace(`/job-portal/${pageName}`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('单点进入失败:', error);
|
||||||
|
setIsError(true);
|
||||||
|
message.error('进入系统失败,请稍后重试');
|
||||||
|
setStatus(STATUS_TEXT.credentialInvalid);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={pageStyle}>
|
||||||
|
<div style={cardStyle}>
|
||||||
|
<Spin size="large" spinning={loading && !isError} />
|
||||||
|
{!loading && isError && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
margin: '0 auto',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: '#fff2f0',
|
||||||
|
color: '#ff4d4f',
|
||||||
|
fontSize: 24,
|
||||||
|
lineHeight: '48px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h1 style={titleStyle}>{isError ? '暂时无法进入' : '正在进入系统'}</h1>
|
||||||
|
<p style={statusStyle}>{status}</p>
|
||||||
|
{!isError && <p style={hintStyle}>{HINT_TEXT}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -32,9 +32,29 @@ export async function exportCmsCompanyList(params?: API.CompanyList.Params) {
|
|||||||
return downLoadXlsx(`/cms/company/export`, { params }, `dict_data_${new Date().getTime()}.xlsx`);
|
return downLoadXlsx(`/cms/company/export`, { params }, `dict_data_${new Date().getTime()}.xlsx`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 解析企业详情接口响应(兼容 data 包裹与平铺字段两种格式) */
|
||||||
|
export function parseCmsCompanyDetailResponse(
|
||||||
|
resp?: { code?: number; data?: API.CompanyList.Company; msg?: string; [key: string]: any },
|
||||||
|
): API.CompanyList.Company | undefined {
|
||||||
|
if (!resp || resp.code !== 200) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (resp.data && typeof resp.data === 'object' && !Array.isArray(resp.data)) {
|
||||||
|
return resp.data;
|
||||||
|
}
|
||||||
|
const { code, msg, data, rows, total, ...rest } = resp;
|
||||||
|
if (Object.keys(rest).length > 0) {
|
||||||
|
return rest as API.CompanyList.Company;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getCmsCompanyDetail(companyId: number | string) {
|
export async function getCmsCompanyDetail(companyId: number | string) {
|
||||||
return request<any>(`/api/cms/company/${companyId}`, {
|
return request<{
|
||||||
|
code: number;
|
||||||
|
msg?: string;
|
||||||
|
data?: API.CompanyList.Company;
|
||||||
|
}>(`/api/cms/company/${companyId}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: { companyId },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/services/jobportal/auth.ts
Normal file
28
src/services/jobportal/auth.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
|
export type SsoCodeLoginData = { token: string; lcToken: string };
|
||||||
|
export type SsoCodeLoginResult = {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: SsoCodeLoginData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ssoCodeLoginRequest = (url: string, code: string) =>
|
||||||
|
request<SsoCodeLoginResult>(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
isToken: false,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: { code },
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 经办端:POST /sso/pcms/code/login */
|
||||||
|
export async function getTjmhToken(code: string) {
|
||||||
|
return ssoCodeLoginRequest('/api/sso/pcms/code/login', code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 互联网端:POST /sso/pc/code/login */
|
||||||
|
export async function getWwTjmHlwToken(code: string) {
|
||||||
|
return ssoCodeLoginRequest('/api/sso/pc/code/login', code);
|
||||||
|
}
|
||||||
47
src/utils/thirdPartyLogin.ts
Normal file
47
src/utils/thirdPartyLogin.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { setSessionToken } from '@/access';
|
||||||
|
import { getTjmhToken, getWwTjmHlwToken } from '@/services/jobportal/auth';
|
||||||
|
import { saveTokenFromUrl } from '@/utils/tokenCache';
|
||||||
|
|
||||||
|
const SESSION_DURATION_MS = 1000 * 12 * 60 * 60;
|
||||||
|
|
||||||
|
export function isThirdPartyTransitionPage(pathname: string): boolean {
|
||||||
|
return (
|
||||||
|
pathname === '/' ||
|
||||||
|
pathname === '/login-tow' ||
|
||||||
|
pathname === '/shihezi/' ||
|
||||||
|
pathname === '/shihezi/login-tow'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 第三方 SSO 换票,须在 getRouters 之前完成 */
|
||||||
|
export async function exchangeThirdPartyCredential(options: {
|
||||||
|
code?: string | null;
|
||||||
|
token?: string | null;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
const { code, token } = options;
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
const tokenResponse = await getTjmhToken(code);
|
||||||
|
const sessionToken = tokenResponse?.data?.token;
|
||||||
|
const lcToken = tokenResponse?.data?.lcToken;
|
||||||
|
if (tokenResponse.code === 200 && sessionToken) {
|
||||||
|
setSessionToken(sessionToken, sessionToken, Date.now() + SESSION_DURATION_MS, lcToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
saveTokenFromUrl();
|
||||||
|
const tokenResponse = await getWwTjmHlwToken(token);
|
||||||
|
const sessionToken = tokenResponse?.data?.token;
|
||||||
|
const lcToken = tokenResponse?.data?.lcToken;
|
||||||
|
if (tokenResponse.code === 200 && sessionToken) {
|
||||||
|
setSessionToken(sessionToken, sessionToken, Date.now() + SESSION_DURATION_MS, lcToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
69
src/utils/tokenCache.ts
Normal file
69
src/utils/tokenCache.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Token缓存工具类
|
||||||
|
* 用于管理互联网端登录的token缓存和URL拼接
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 缓存键名
|
||||||
|
const TOKEN_CACHE_KEY = 'internet_token';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从URL参数中获取token并保存到缓存
|
||||||
|
*/
|
||||||
|
export function saveTokenFromUrl(): void {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const token = urlParams.get('token');
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
localStorage.setItem(TOKEN_CACHE_KEY, token);
|
||||||
|
console.log('Token已保存到缓存:', token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存中获取token
|
||||||
|
*/
|
||||||
|
export function getTokenFromCache(): string | null {
|
||||||
|
return localStorage.getItem(TOKEN_CACHE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除缓存中的token
|
||||||
|
*/
|
||||||
|
export function clearTokenCache(): void {
|
||||||
|
localStorage.removeItem(TOKEN_CACHE_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为URL添加token参数
|
||||||
|
* @param url 原始URL
|
||||||
|
* @param token token值
|
||||||
|
* @returns 添加了token参数的URL
|
||||||
|
*/
|
||||||
|
export function appendTokenToUrl(url: string, token: string | null): string {
|
||||||
|
if (!token) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查URL是否已经有查询参数
|
||||||
|
const hasQuery = url.includes('?');
|
||||||
|
const separator = hasQuery ? '&' : '?';
|
||||||
|
|
||||||
|
return `${url}${separator}token=${encodeURIComponent(token)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理导航跳转,自动添加token参数
|
||||||
|
* @param url 目标URL
|
||||||
|
*/
|
||||||
|
export function navigateWithToken(url: string): void {
|
||||||
|
const token = getTokenFromCache();
|
||||||
|
const finalUrl = appendTokenToUrl(url, token);
|
||||||
|
|
||||||
|
console.log('跳转URL:', finalUrl);
|
||||||
|
window.open(finalUrl, '_blank');
|
||||||
|
// 对于需要确保新标签页被浏览器允许打开的情况,可以使用以下方式:
|
||||||
|
// const newWindow = window.open();
|
||||||
|
// if (newWindow) {
|
||||||
|
// newWindow.location.assign(finalUrl);
|
||||||
|
// }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user