Compare commits
16 Commits
0e66061b40
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ae49516b4 | ||
|
|
063905292c | ||
|
|
668f853303 | ||
|
|
ab8ee802db | ||
|
|
aa9584dbc9 | ||
|
|
d869713329 | ||
|
|
2a930bf682 | ||
|
|
e7ad0c1567 | ||
|
|
5b6393a37d | ||
|
|
e034f117e8 | ||
|
|
49f48b132d | ||
|
|
388dc6c9fa | ||
|
|
0fef7b57a8 | ||
|
|
16f456d2f2 | ||
|
|
6007a69e8a | ||
|
|
4b0c7033e2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ _roadhog-api-doc
|
|||||||
|
|
||||||
# production
|
# production
|
||||||
/dist
|
/dist
|
||||||
|
/qing
|
||||||
/qingdao
|
/qingdao
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
|||||||
138
README.md
138
README.md
@@ -1,138 +0,0 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
Language : 🇺🇸 | [🇨🇳](./README.zh-CN.md) | [🇷🇺](./README.ru-RU.md) | [🇹🇷](./README.tr-TR.md) | [🇯🇵](./README.ja-JP.md) | [🇫🇷](./README.fr-FR.md) | [🇵🇹](./README.pt-BR.md) | [🇸🇦](./README.ar-DZ.md)
|
|
||||||
|
|
||||||
<h1 align="center">Ant Design Pro</h1>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
An out-of-box UI solution for enterprise applications as a React boilerplate.
|
|
||||||
|
|
||||||
[](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master)  
|
|
||||||
|
|
||||||
[](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [](http://umijs.org/) 
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
- Preview: http://preview.pro.ant.design
|
|
||||||
- Home Page: http://pro.ant.design
|
|
||||||
- Documentation: http://pro.ant.design/docs/getting-started
|
|
||||||
- ChangeLog: http://pro.ant.design/docs/changelog
|
|
||||||
- FAQ: http://pro.ant.design/docs/faq
|
|
||||||
- Mirror Site in China: http://ant-design-pro.gitee.io
|
|
||||||
|
|
||||||
## 5.0 is out! 🎉🎉🎉
|
|
||||||
|
|
||||||
[Ant Design Pro 5.0.0](https://github.com/ant-design/ant-design-pro/issues/8656)
|
|
||||||
|
|
||||||
## Translation Recruitment :loudspeaker:
|
|
||||||
|
|
||||||
We need your help: https://github.com/ant-design/ant-design-pro/issues/120
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- :bulb: **TypeScript**: A language for application-scale JavaScript
|
|
||||||
- :scroll: **Blocks**: Build page with block template
|
|
||||||
- :gem: **Neat Design**: Follow [Ant Design specification](http://ant.design/)
|
|
||||||
- :triangular_ruler: **Common Templates**: Typical templates for enterprise applications
|
|
||||||
- :rocket: **State of The Art Development**: Newest development stack of React/umi/dva/antd
|
|
||||||
- :iphone: **Responsive**: Designed for variable screen sizes
|
|
||||||
- :art: **Theming**: Customizable theme with simple config
|
|
||||||
- :globe_with_meridians: **International**: Built-in i18n solution
|
|
||||||
- :gear: **Best Practices**: Solid workflow to make your code healthy
|
|
||||||
- :1234: **Mock development**: Easy to use mock development solution
|
|
||||||
- :white_check_mark: **UI Test**: Fly safely with unit and e2e tests
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
|
|
||||||
```
|
|
||||||
- Dashboard
|
|
||||||
- Analytic
|
|
||||||
- Monitor
|
|
||||||
- Workspace
|
|
||||||
- Form
|
|
||||||
- Basic Form
|
|
||||||
- Step Form
|
|
||||||
- Advanced From
|
|
||||||
- List
|
|
||||||
- Standard Table
|
|
||||||
- Standard List
|
|
||||||
- Card List
|
|
||||||
- Search List (Project/Applications/Article)
|
|
||||||
- Profile
|
|
||||||
- Simple Profile
|
|
||||||
- Advanced Profile
|
|
||||||
- Account
|
|
||||||
- Account Center
|
|
||||||
- Account Settings
|
|
||||||
- Result
|
|
||||||
- Success
|
|
||||||
- Failed
|
|
||||||
- Exception
|
|
||||||
- 403
|
|
||||||
- 404
|
|
||||||
- 500
|
|
||||||
- User
|
|
||||||
- Login
|
|
||||||
- Register
|
|
||||||
- Register Result
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Use bash
|
|
||||||
|
|
||||||
We provide pro-cli to quickly initialize scaffolding.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# use npm
|
|
||||||
npm i @ant-design/pro-cli -g
|
|
||||||
pro create myapp
|
|
||||||
```
|
|
||||||
|
|
||||||
select umi version
|
|
||||||
|
|
||||||
```shell
|
|
||||||
🐂 Use umi@4 or umi@3 ? (Use arrow keys)
|
|
||||||
❯ umi@4
|
|
||||||
umi@3
|
|
||||||
```
|
|
||||||
|
|
||||||
> If the umi@4 version is selected, full blocks are not yet supported.
|
|
||||||
|
|
||||||
If you choose umi@3, you can also choose the pro template. Pro is the basic template, which only provides the basic content of the framework operation. Complete contains all blocks, which is not suitable for secondary development as a basic template.
|
|
||||||
|
|
||||||
```shell
|
|
||||||
? 🚀 Full or a simple scaffold? (Use arrow keys)
|
|
||||||
❯ simple
|
|
||||||
complete
|
|
||||||
```
|
|
||||||
|
|
||||||
Install dependencies:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ cd myapp && tyarn
|
|
||||||
// or
|
|
||||||
$ cd myapp && npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
## Browsers support
|
|
||||||
|
|
||||||
Modern browsers.
|
|
||||||
|
|
||||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Any type of contribution is welcome, here are some examples of how you may contribute to this project:
|
|
||||||
|
|
||||||
- Use Ant Design Pro in your daily work.
|
|
||||||
- Submit [issues](http://github.com/ant-design/ant-design-pro/issues) to report bugs or ask questions.
|
|
||||||
- Propose [pull requests](http://github.com/ant-design/ant-design-pro/pulls) to improve our code.
|
|
||||||
=======
|
|
||||||
# qingdao-admin
|
|
||||||
|
|
||||||
>>>>>>> 997802589330c49d3abc4f122ed00ce04a3c9601
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default defineConfig({
|
|||||||
* @name layout 插件
|
* @name layout 插件
|
||||||
* @doc https://umijs.org/docs/max/layout-menu
|
* @doc https://umijs.org/docs/max/layout-menu
|
||||||
*/
|
*/
|
||||||
title: '青岛智慧就业服务系统',
|
title: '石河子智慧就业服务系统',
|
||||||
layout: {
|
layout: {
|
||||||
locale: false,
|
locale: false,
|
||||||
...defaultSettings,
|
...defaultSettings,
|
||||||
@@ -125,7 +125,7 @@ export default defineConfig({
|
|||||||
*/
|
*/
|
||||||
headScripts: [
|
headScripts: [
|
||||||
// 解决首次加载时白屏的问题
|
// 解决首次加载时白屏的问题
|
||||||
{ src: '/qingdao/scripts/loading.js', async: true },
|
{ src: '/qing/scripts/loading.js', async: true },
|
||||||
],
|
],
|
||||||
//================ pro 插件配置 =================
|
//================ pro 插件配置 =================
|
||||||
presets: ['umi-presets-pro'],
|
presets: ['umi-presets-pro'],
|
||||||
@@ -151,9 +151,9 @@ export default defineConfig({
|
|||||||
mfsu: {
|
mfsu: {
|
||||||
strategy: 'normal',
|
strategy: 'normal',
|
||||||
},
|
},
|
||||||
outputPath: 'qingdao',
|
outputPath: 'qing',
|
||||||
base: '/qingdao/',
|
base: '/qing/',
|
||||||
publicPath: '/qingdao/',
|
publicPath: '/qing/',
|
||||||
esbuildMinifyIIFE: true,
|
esbuildMinifyIIFE: true,
|
||||||
requestRecord: {},
|
requestRecord: {},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const Settings: ProLayoutProps & {
|
|||||||
fixedHeader: false,
|
fixedHeader: false,
|
||||||
fixSiderbar: true,
|
fixSiderbar: true,
|
||||||
colorWeak: false,
|
colorWeak: false,
|
||||||
title: '青岛智慧就业服务系统',
|
title: '石河子智慧就业服务系统',
|
||||||
pwa: true,
|
pwa: true,
|
||||||
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
logo: 'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||||
iconfontUrl: '',
|
iconfontUrl: '',
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ export default {
|
|||||||
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||||
'/api/': {
|
'/api/': {
|
||||||
// 要代理的地址
|
// 要代理的地址
|
||||||
target: 'https://qd.zhaopinzao8dian.com/api',
|
target: 'http://36.105.163.21:30083/rgpp/api',
|
||||||
// 配置了这个可以从 http 代理到 https
|
// 配置了这个可以从 http 代理到 https
|
||||||
// 依赖 origin 的功能可能需要这个,比如 cookie
|
// 依赖 origin 的功能可能需要这个,比如 cookie
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
pathRewrite: { '^/api': '' },
|
pathRewrite: { '^/api': '' },
|
||||||
},
|
},
|
||||||
'/profile/avatar/': {
|
'/profile/avatar/': {
|
||||||
target: 'https://qd.zhaopinzao8dian.com/api',
|
target: 'http://36.105.163.21:30083/rgpp/api',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
90
src/app.tsx
90
src/app.tsx
@@ -6,7 +6,7 @@ import type { RunTimeLayoutConfig } from '@umijs/max';
|
|||||||
import { history } from '@umijs/max';
|
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,setSessionToken, getRefreshToken, getTokenExpireTime } from './access';
|
||||||
import {
|
import {
|
||||||
getRemoteMenu,
|
getRemoteMenu,
|
||||||
getRoutersInfo,
|
getRoutersInfo,
|
||||||
@@ -29,11 +29,11 @@ const loginOut = async () => {
|
|||||||
const redirect = urlParams.get('redirect');
|
const redirect = urlParams.get('redirect');
|
||||||
// Note: There may be security issues, please note
|
// Note: There may be security issues, please note
|
||||||
console.log('redirect', window.location.pathname, redirect);
|
console.log('redirect', window.location.pathname, redirect);
|
||||||
if (window.location.pathname !== '/qingdao/user/login' && !redirect) {
|
if (window.location.pathname !== '/qing/user/login' && !redirect) {
|
||||||
history.replace({
|
history.replace({
|
||||||
pathname: '/user/login',
|
pathname: '/user/login',
|
||||||
search: stringify({
|
search: stringify({
|
||||||
redirect: pathname.replace('/qingdao', '') + search,
|
redirect: pathname.replace('/qing', '') + search,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -64,12 +64,53 @@ export async function getInitialState(): Promise<{
|
|||||||
} as API.CurrentUser;
|
} as API.CurrentUser;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
// 如果获取用户信息失败,清除token并跳转到登录页
|
||||||
|
clearSessionToken();
|
||||||
history.push(PageEnum.LOGIN);
|
history.push(PageEnum.LOGIN);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
// 如果不是登录页面,执行
|
|
||||||
|
// 检查URL参数中是否包含token参数
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
|
const tokenFromUrl = urlParams.get('token');
|
||||||
|
|
||||||
|
if (tokenFromUrl) {
|
||||||
|
try {
|
||||||
|
// 处理token,去掉Bearer前缀(如果存在)
|
||||||
|
let processedToken = tokenFromUrl.trim();
|
||||||
|
if (processedToken.startsWith('Bearer ')) {
|
||||||
|
processedToken = processedToken.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置token(
|
||||||
|
const current = new Date();
|
||||||
|
const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60); // 12小时过期
|
||||||
|
setSessionToken(processedToken, processedToken, expireTime);
|
||||||
|
|
||||||
|
// 尝试获取用户信息
|
||||||
|
const currentUser = await fetchUserInfo();
|
||||||
|
if (currentUser) {
|
||||||
|
// 成功获取用户信息,清除URL中的token参数并跳转
|
||||||
|
const newSearch = new URLSearchParams(urlParams);
|
||||||
|
newSearch.delete('token');
|
||||||
|
history.push(urlParams.get('redirect') || '/');
|
||||||
|
setTimeout(() => history.go(0), 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchUserInfo,
|
||||||
|
currentUser,
|
||||||
|
settings: defaultSettings as Partial<LayoutSettings>,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理URL token失败:', error);
|
||||||
|
clearSessionToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是登录页面且没有token参数,正常执行
|
||||||
if (location.pathname !== PageEnum.LOGIN) {
|
if (location.pathname !== PageEnum.LOGIN) {
|
||||||
const currentUser = await fetchUserInfo();
|
const currentUser = await fetchUserInfo();
|
||||||
return {
|
return {
|
||||||
@@ -78,6 +119,7 @@ export async function getInitialState(): Promise<{
|
|||||||
settings: defaultSettings as Partial<LayoutSettings>,
|
settings: defaultSettings as Partial<LayoutSettings>,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchUserInfo,
|
fetchUserInfo,
|
||||||
settings: defaultSettings as Partial<LayoutSettings>,
|
settings: defaultSettings as Partial<LayoutSettings>,
|
||||||
@@ -116,6 +158,17 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
|
|||||||
footerRender: () => <Footer />,
|
footerRender: () => <Footer />,
|
||||||
onPageChange: () => {
|
onPageChange: () => {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
|
|
||||||
|
// 检查URL参数中是否包含token
|
||||||
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
|
const tokenFromUrl = urlParams.get('token');
|
||||||
|
|
||||||
|
if (tokenFromUrl) {
|
||||||
|
// 如果有token参数,不执行跳转逻辑,由getInitialState处理
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有token参数,执行原有的逻辑
|
||||||
// 如果没有登录,重定向到 login
|
// 如果没有登录,重定向到 login
|
||||||
if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) {
|
if (!initialState?.currentUser && location.pathname !== PageEnum.LOGIN) {
|
||||||
history.push(PageEnum.LOGIN);
|
history.push(PageEnum.LOGIN);
|
||||||
@@ -197,14 +250,29 @@ export async function patchClientRoutes({ routes }) {
|
|||||||
|
|
||||||
export async function render(oldRender: () => void) {
|
export async function render(oldRender: () => void) {
|
||||||
console.log('render get routers', oldRender);
|
console.log('render get routers', oldRender);
|
||||||
const token = getAccessToken();
|
|
||||||
if (!token || token?.length === 0) {
|
// 检查URL参数中是否包含token
|
||||||
oldRender();
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
return;
|
const tokenFromUrl = urlParams.get('token');
|
||||||
|
|
||||||
|
if (tokenFromUrl) {
|
||||||
|
// 如果URL中有token参数,已经在getInitialState中处理过了
|
||||||
|
const token = getAccessToken();
|
||||||
|
if (!token || token?.length === 0) {
|
||||||
|
oldRender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 原有逻辑:检查本地存储的token
|
||||||
|
const token = getAccessToken();
|
||||||
|
if (!token || token?.length === 0) {
|
||||||
|
oldRender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await getRoutersInfo().then((res) => {
|
await getRoutersInfo().then((res) => {
|
||||||
console.log('render get routers', 123);
|
console.log('render get routers', 123);
|
||||||
|
|
||||||
setRemoteMenu(res);
|
setRemoteMenu(res);
|
||||||
oldRender();
|
oldRender();
|
||||||
});
|
});
|
||||||
@@ -218,8 +286,8 @@ export async function render(oldRender: () => void) {
|
|||||||
const checkRegion = 5 * 60 * 1000;
|
const checkRegion = 5 * 60 * 1000;
|
||||||
export const request = {
|
export const request = {
|
||||||
...errorConfig,
|
...errorConfig,
|
||||||
baseURL: process.env.NODE_ENV === 'development' ? '' : 'https://qd.zhaopinzao8dian.com/api',
|
baseURL: process.env.NODE_ENV === 'development' ? '' : 'http://36.105.163.21:30083/rgpp/api',
|
||||||
// baseURL: 'http://39.98.44.136:8080',
|
// baseURL: 'http://36.105.163.21:30083/rgpp/api',
|
||||||
// baseURL:
|
// baseURL:
|
||||||
// process.env.NODE_ENV === 'development'
|
// process.env.NODE_ENV === 'development'
|
||||||
// ? 'http://10.213.6.207:19010'
|
// ? 'http://10.213.6.207:19010'
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import { GithubOutlined } from '@ant-design/icons';
|
import { GithubOutlined } from '@ant-design/icons';
|
||||||
import { DefaultFooter } from '@ant-design/pro-components';
|
import { DefaultFooter } from '@ant-design/pro-components';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {getYear} from '@/utils/tools'
|
import { getYear } from '@/utils/tools';
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<DefaultFooter
|
<DefaultFooter
|
||||||
copyright={` ${getYear()} 青岛智慧就业服务系统`}
|
copyright={` ${getYear()} 石河子智慧就业服务系统`}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: 'none',
|
||||||
}}
|
}}
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
key: '青岛政务网',
|
key: '石河子政务网',
|
||||||
title: '青岛政务网',
|
title: '石河子政务网',
|
||||||
href: 'http://www.qingdao.gov.cn/',
|
href: 'http://www.shihezi.gov.cn/',
|
||||||
blankTarget: true,
|
blankTarget: true,
|
||||||
},{
|
},
|
||||||
key: '青岛市人力资源和社会保障局',
|
{
|
||||||
title: '青岛市人力资源和社会保障局',
|
key: '石河子市人力资源和社会保障局',
|
||||||
href: 'https://hrss.qingdao.gov.cn/',
|
title: '石河子市人力资源和社会保障局',
|
||||||
|
href: 'https://hrss.shihezi.gov.cn/',
|
||||||
blankTarget: true,
|
blankTarget: true,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const ProFromMap: React.FC<MapProps> = ({ open, onSelect, onCancel }) => {
|
|||||||
// 注意:输入提示插件2.0版本需引入AMap.AutoComplete,而1.4版本应使用AMap.Autocomplete
|
// 注意:输入提示插件2.0版本需引入AMap.AutoComplete,而1.4版本应使用AMap.Autocomplete
|
||||||
// 实例化AutoComplete
|
// 实例化AutoComplete
|
||||||
autoCompleteRef.current = new AMap.AutoComplete({
|
autoCompleteRef.current = new AMap.AutoComplete({
|
||||||
city: '370200', // 青岛市
|
city: '370200', // 石河子市
|
||||||
citylimit: false,
|
citylimit: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -79,7 +79,7 @@ const ProFromMap: React.FC<MapProps> = ({ open, onSelect, onCancel }) => {
|
|||||||
geocoderRef.current = new AMap.Geocoder({
|
geocoderRef.current = new AMap.Geocoder({
|
||||||
extensions: 'base',
|
extensions: 'base',
|
||||||
batch: false,
|
batch: false,
|
||||||
city: '370200', // 青岛市
|
city: '370200', // 石河子市
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ const ProFromMap: React.FC<MapProps> = ({ open, onSelect, onCancel }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchLocation = async (keyWords: string) => {
|
const searchLocation = async (keyWords: string) => {
|
||||||
const keywordsToSearch = keyWords || '青岛';
|
const keywordsToSearch = keyWords || '石河子';
|
||||||
const resData = await autoInput(keywordsToSearch);
|
const resData = await autoInput(keywordsToSearch);
|
||||||
locationList.current = resData;
|
locationList.current = resData;
|
||||||
setLocationOptions(resData);
|
setLocationOptions(resData);
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
|
|||||||
/** 此方法会跳转到 redirect 参数所在的位置 */
|
/** 此方法会跳转到 redirect 参数所在的位置 */
|
||||||
const redirect = urlParams.get('redirect');
|
const redirect = urlParams.get('redirect');
|
||||||
// Note: There may be security issues, please note
|
// Note: There may be security issues, please note
|
||||||
console.log('redirect', window.location.pathname, redirect)
|
console.log('redirect', window.location.pathname, redirect);
|
||||||
if (window.location.pathname !== '/qingdao/user/login' && !redirect) {
|
if (window.location.pathname !== '/qing/user/login' && !redirect) {
|
||||||
history.replace({
|
history.replace({
|
||||||
pathname: '/user/login',
|
pathname: '/user/login',
|
||||||
search: stringify({
|
search: stringify({
|
||||||
redirect: pathname.replace('/qingdao', '') + search,
|
redirect: pathname.replace('/qing', '') + search,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/pages/CaseManagementAndTracking/CaseManagement/index.tsx
Normal file
177
src/pages/CaseManagementAndTracking/CaseManagement/index.tsx
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import React, { Fragment, useRef, useState } from 'react';
|
||||||
|
import { Button, FormInstance, message } from 'antd';
|
||||||
|
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||||
|
|
||||||
|
export default function CaseManagement() {
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
const formTableRef = useRef<FormInstance>()
|
||||||
|
|
||||||
|
const columns: ProColumns<API.StorageDetection.StorageItem>[] = [
|
||||||
|
{
|
||||||
|
title: '序号',
|
||||||
|
dataIndex: 'storageDate',
|
||||||
|
valueType: 'text',
|
||||||
|
render: (_, record, index) => index + 1,
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '投诉编号',
|
||||||
|
dataIndex: 'detectionId',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '行业类别',
|
||||||
|
dataIndex: 'industry',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '用工所在地',
|
||||||
|
dataIndex: 'address',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '投诉单位',
|
||||||
|
dataIndex: 'unit',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '投诉人',
|
||||||
|
dataIndex: 'websiteName',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '投诉人手机号',
|
||||||
|
dataIndex: 'number',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '欠薪人数',
|
||||||
|
dataIndex: 'wage',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '欠薪金额(万元)',
|
||||||
|
dataIndex: 'amount',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '欠薪日期',
|
||||||
|
dataIndex: 'storageDate',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '欠薪区域',
|
||||||
|
dataIndex: 'wageArrearsArea',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '案件状态',
|
||||||
|
dataIndex: 'failedReason',
|
||||||
|
valueType: 'text',
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作状态',
|
||||||
|
dataIndex: 'failedReason',
|
||||||
|
valueType: 'text',
|
||||||
|
hideInSearch: true,
|
||||||
|
align: 'center',
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: '操作',
|
||||||
|
// hideInSearch: true,
|
||||||
|
// align: 'center',
|
||||||
|
// dataIndex: 'detectionId',
|
||||||
|
// width: 120,
|
||||||
|
// // render: (detectionId, record) => (
|
||||||
|
// // <div style={{ display: 'flex', justifyContent: 'center', gap: 8 }}>
|
||||||
|
// // <Button
|
||||||
|
// // type="link"
|
||||||
|
// // size="small"
|
||||||
|
// // key="view"
|
||||||
|
// // icon={<EyeOutlined />}
|
||||||
|
// // loading={loading}
|
||||||
|
// // hidden={!access.hasPerms('recruitmentDataCollection:jobMonitor:view')}
|
||||||
|
// // onClick={() => handleViewDetail(detectionId)}
|
||||||
|
// // >
|
||||||
|
// // 查看详情
|
||||||
|
// // </Button>
|
||||||
|
// // </div>
|
||||||
|
// // ),
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ width: '100%', float: 'right' }}>
|
||||||
|
<ProTable<API.StorageDetection.StorageItem>
|
||||||
|
actionRef={actionRef}
|
||||||
|
formRef={formTableRef}
|
||||||
|
rowKey="detectionId"
|
||||||
|
key="storageDetectionIndex"
|
||||||
|
columns={columns}
|
||||||
|
search={{ labelWidth: 'auto' }}
|
||||||
|
request={async (params) => {
|
||||||
|
// 模拟 API 响应
|
||||||
|
return {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
detectionId: '230',
|
||||||
|
storageDate: '2023-01-01',
|
||||||
|
storageDetail: '存储详情1',
|
||||||
|
failedReason: '正在处理',
|
||||||
|
industry: '销售',
|
||||||
|
address: '石河子',
|
||||||
|
unit: '石河子市水利局',
|
||||||
|
websiteName: '测试1',
|
||||||
|
number: '1234567890',
|
||||||
|
wage: '2',
|
||||||
|
amount: '1111',
|
||||||
|
wageArrearsArea: '石河子市',
|
||||||
|
|
||||||
|
// ... 其他字段
|
||||||
|
},
|
||||||
|
{
|
||||||
|
detectionId: '231',
|
||||||
|
storageDate: '2023-01-01',
|
||||||
|
storageDetail: '存储详情1',
|
||||||
|
failedReason: '正在处理',
|
||||||
|
industry: '销售',
|
||||||
|
address: '石河子',
|
||||||
|
unit: '石河子市水利局',
|
||||||
|
websiteName: '测试1',
|
||||||
|
number: '1234567890',
|
||||||
|
wage: '2',
|
||||||
|
amount: '1111',
|
||||||
|
wageArrearsArea: '石河子市',
|
||||||
|
|
||||||
|
// ... 其他字段
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 2,
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@/services/company/list';
|
} 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 { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||||
|
|
||||||
// 详情查看组件
|
// 详情查看组件
|
||||||
const CompanyDetailModal = ({
|
const CompanyDetailModal = ({
|
||||||
@@ -20,11 +21,13 @@ const CompanyDetailModal = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
record,
|
record,
|
||||||
scaleEnum,
|
scaleEnum,
|
||||||
|
industryEnum,
|
||||||
}: {
|
}: {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
record?: API.CompanyList.Company;
|
record?: API.CompanyList.Company;
|
||||||
scaleEnum: Record<string, any>;
|
scaleEnum: Record<string, any>;
|
||||||
|
industryEnum: Record<string, any>;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -40,7 +43,9 @@ const CompanyDetailModal = ({
|
|||||||
>
|
>
|
||||||
<Descriptions column={1} bordered>
|
<Descriptions column={1} bordered>
|
||||||
<Descriptions.Item label="公司名称">{record?.name}</Descriptions.Item>
|
<Descriptions.Item label="公司名称">{record?.name}</Descriptions.Item>
|
||||||
<Descriptions.Item label="公司行业">{record?.industry}</Descriptions.Item>
|
<Descriptions.Item label="公司行业">
|
||||||
|
<DictTag enums={industryEnum} value={record?.industry} />
|
||||||
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="公司规模">
|
<Descriptions.Item label="公司规模">
|
||||||
<DictTag enums={scaleEnum} value={record?.scale} />
|
<DictTag enums={scaleEnum} value={record?.scale} />
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
@@ -60,6 +65,7 @@ function ManagementList() {
|
|||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [detailVisible, setDetailVisible] = useState<boolean>(false);
|
const [detailVisible, setDetailVisible] = useState<boolean>(false);
|
||||||
const [scaleEnum, setScaleEnum] = useState<Record<string, any>>({});
|
const [scaleEnum, setScaleEnum] = useState<Record<string, any>>({});
|
||||||
|
const [industryEnum, setIndustryEnum] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
const handleRemoveOne = async (jobId: string) => {
|
const handleRemoveOne = async (jobId: string) => {
|
||||||
const hide = message.loading('正在删除');
|
const hide = message.loading('正在删除');
|
||||||
@@ -96,8 +102,28 @@ function ManagementList() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDictValueEnum('scale', true, true).then((data) => {
|
getDictValueEnum('scale', true, true).then((data) => {
|
||||||
|
console.log(data,'______')
|
||||||
setScaleEnum(data);
|
setScaleEnum(data);
|
||||||
});
|
});
|
||||||
|
getCmsIndustryTreeList().then((res) => {
|
||||||
|
|
||||||
|
let dict = []
|
||||||
|
try {
|
||||||
|
res.data.forEach(item => {
|
||||||
|
dict[item.id] = {
|
||||||
|
label: item.label,
|
||||||
|
text: item.label,
|
||||||
|
value: item.id,
|
||||||
|
listClass: "default",
|
||||||
|
status: "default",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
dict = []
|
||||||
|
}
|
||||||
|
console.log(dict,'+++++')
|
||||||
|
setIndustryEnum(dict);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const columns: ProColumns<API.CompanyList.Company>[] = [
|
const columns: ProColumns<API.CompanyList.Company>[] = [
|
||||||
@@ -272,6 +298,7 @@ function ManagementList() {
|
|||||||
}}
|
}}
|
||||||
record={currentRow}
|
record={currentRow}
|
||||||
scaleEnum={scaleEnum}
|
scaleEnum={scaleEnum}
|
||||||
|
industryEnum={industryEnum}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,74 +1,333 @@
|
|||||||
import { Modal } from 'antd';
|
import { Modal, Row, Col, Card, Statistic } from 'antd';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import * as echarts from 'echarts';
|
|
||||||
|
|
||||||
export type ListFormProps = {
|
export type ListFormProps = {
|
||||||
onClose: (flag?: boolean, formVals?: unknown) => void;
|
onClose: (flag?: boolean, formVals?: unknown) => void;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
values?: Partial<API.MobileUser.ListRow>;
|
loading: boolean;
|
||||||
matching?: any;
|
competitivenessData?: API.ManagementList.CompetitivenessData;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listEdit: React.FC<ListFormProps> = (props) => {
|
const Detail: React.FC<ListFormProps> = (props) => {
|
||||||
const { open, values, matching } = props;
|
const { open, loading, competitivenessData } = props;
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const [currentStep, setCurrentStep] = useState<number>(0);
|
||||||
|
const [isStyleInjected, setIsStyleInjected] = useState<boolean>(false);
|
||||||
|
|
||||||
const mapRef = useRef(null);
|
const matchingDegree = ['一般', '良好', '优秀', '极好'];
|
||||||
|
|
||||||
|
// 注入进度条样式
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isStyleInjected) {
|
||||||
|
const styleId = 'progress-bar-styles';
|
||||||
|
if (!document.getElementById(styleId)) {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = styleId;
|
||||||
|
|
||||||
|
// 生成样式
|
||||||
|
let styleContent = `
|
||||||
|
.progress-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-text {
|
||||||
|
margin-top: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #999999;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item {
|
||||||
|
width: 25%;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #eee;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item.active {
|
||||||
|
background: linear-gradient(to right, #256bfa, #8c68ff);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 生成半透明进度样式
|
||||||
|
for (let i = 0; i <= 100; i++) {
|
||||||
|
styleContent += `
|
||||||
|
.progress-item.half${i}::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(to right, #256bfa ${i}%, #eaeaea ${i}%);
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
style.textContent = styleContent;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
setIsStyleInjected(true);
|
||||||
|
}
|
||||||
|
}, [isStyleInjected]);
|
||||||
|
|
||||||
|
// 绘制雷达图
|
||||||
|
const drawRadarChart = () => {
|
||||||
|
if (!canvasRef.current || !competitivenessData?.radarChart) return;
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
// 清空画布
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const { skill, experience, education, salary, age, location } = competitivenessData.radarChart;
|
||||||
|
|
||||||
|
//
|
||||||
|
const labels = ['学历', '年龄', '工作地', '工作经验', '期望薪资'];
|
||||||
|
const data = [education, age, location, experience, salary].map((item) => item * 0.05);
|
||||||
|
|
||||||
|
// 绘制参数
|
||||||
|
const width = 120;
|
||||||
|
const height = 120;
|
||||||
|
const centerX = canvas.width / 2;
|
||||||
|
const centerY = canvas.height / 2 + 10;
|
||||||
|
const colors = ['#e6e6e6', '#e6e6e6', '#e6e6e6', '#e6e6e6', '#e6e6e6'];
|
||||||
|
const maxScore = 5;
|
||||||
|
const angleStep = (2 * Math.PI) / labels.length;
|
||||||
|
|
||||||
|
// 1. 绘制背景圆形
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(centerX, centerY, width, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = '#FFFFFF';
|
||||||
|
ctx.fill();
|
||||||
|
ctx.closePath();
|
||||||
|
|
||||||
|
// 2. 绘制5层多边形
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let i = 5; i > 0; i--) {
|
||||||
|
ctx.strokeStyle = colors[i - 1];
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
labels.forEach((_, index) => {
|
||||||
|
const x = centerX + (width / 5) * i * Math.cos(angleStep * index - Math.PI / 2);
|
||||||
|
const y = centerY + (height / 5) * i * Math.sin(angleStep * index - Math.PI / 2);
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 绘制坐标轴
|
||||||
|
ctx.strokeStyle = '#e6e6e6';
|
||||||
|
labels.forEach((_, index) => {
|
||||||
|
ctx.beginPath();
|
||||||
|
const x1 = centerX + width * 0.6 * Math.cos(angleStep * index - Math.PI / 2);
|
||||||
|
const y1 = centerY + height * 0.6 * Math.sin(angleStep * index - Math.PI / 2);
|
||||||
|
const x = centerX + width * Math.cos(angleStep * index - Math.PI / 2);
|
||||||
|
const y = centerY + height * Math.sin(angleStep * index - Math.PI / 2);
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. 绘制数据区域
|
||||||
|
const pointList: Array<{ x: number; y: number }> = [];
|
||||||
|
ctx.strokeStyle = '#256BFA';
|
||||||
|
ctx.fillStyle = 'rgba(37,107,250,0.24)';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
data.forEach((score, index) => {
|
||||||
|
const x = centerX + width * (score / maxScore) * Math.cos(angleStep * index - Math.PI / 2);
|
||||||
|
const y = centerY + height * (score / maxScore) * Math.sin(angleStep * index - Math.PI / 2);
|
||||||
|
pointList.push({ x, y });
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 5. 绘制数据点
|
||||||
|
ctx.fillStyle = '#256BFA';
|
||||||
|
pointList.forEach((point) => {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. 绘制标签
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.font = 'bold 12px sans-serif';
|
||||||
|
|
||||||
|
labels.forEach((label, index) => {
|
||||||
|
const x = centerX + (width + 30) * Math.cos(angleStep * index - Math.PI / 2);
|
||||||
|
const y = centerY + (height + 26) * Math.sin(angleStep * index - Math.PI / 2);
|
||||||
|
ctx.fillText(label, x, y);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取进度条类名
|
||||||
|
const getProgressClass = (index: number) => {
|
||||||
|
if (currentStep === 0) return '';
|
||||||
|
|
||||||
|
const floorIndex = Math.floor(currentStep);
|
||||||
|
|
||||||
|
if (index < floorIndex) {
|
||||||
|
return 'active';
|
||||||
|
} else if (index === floorIndex) {
|
||||||
|
const decimal = currentStep % 1;
|
||||||
|
const percent = Math.round(decimal * 100);
|
||||||
|
return `half${percent}`;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(matching);
|
if (competitivenessData?.matchScore !== undefined) {
|
||||||
if (matching && open) {
|
// 计算匹配度
|
||||||
const myCharts = echarts.init(mapRef.current);
|
const score = competitivenessData.matchScore;
|
||||||
const { educationMatch, maxSimilarity, salaryMatch, areaMatch } = matching;
|
const step = score * 0.04;
|
||||||
// educationMatch
|
setCurrentStep(step);
|
||||||
// maxSimilarity
|
|
||||||
// salaryMatch
|
|
||||||
// areaMatch
|
|
||||||
myCharts.setOption({
|
|
||||||
radar: {
|
|
||||||
shape: 'circle',
|
|
||||||
indicator: [
|
|
||||||
{ name: '学历', max: 1 },
|
|
||||||
{ name: '薪资', max: 1 },
|
|
||||||
{ name: '区域', max: 1 },
|
|
||||||
{ name: '内容', max: 1 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: '综合匹配度',
|
|
||||||
type: 'radar',
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
value: [educationMatch, salaryMatch, areaMatch, maxSimilarity],
|
|
||||||
name: 'Allocated Budget',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [matching]);
|
}, [competitivenessData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && competitivenessData && canvasRef.current) {
|
||||||
|
// 初始化Canvas
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
canvas.width = 400;
|
||||||
|
canvas.height = 320;
|
||||||
|
drawRadarChart();
|
||||||
|
}
|
||||||
|
}, [open, competitivenessData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title="匹配详情"
|
title="竞争力分析"
|
||||||
width={400}
|
width={700}
|
||||||
open={props.open}
|
open={open}
|
||||||
footer={''}
|
footer={null}
|
||||||
onCancel={() => props.onClose()}
|
onCancel={() => props.onClose()}
|
||||||
maskClosable={true}
|
maskClosable={true}
|
||||||
|
loading={loading}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
minHeight: '600px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<Row gutter={[16, 16]}>
|
||||||
style={{
|
{/* 统计数据 */}
|
||||||
display: 'flex',
|
<Col span={24}>
|
||||||
alignItems: 'center',
|
<Row gutter={16}>
|
||||||
justifyContent: 'center',
|
<Col span={6}>
|
||||||
}}
|
<Card>
|
||||||
>
|
<Statistic title="总申请人数" value={competitivenessData?.totalApplicants || 0} />
|
||||||
<div style={{ width: '300px', height: '300px' }} ref={mapRef}></div>
|
</Card>
|
||||||
</div>
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="匹配分数"
|
||||||
|
value={competitivenessData?.matchScore || 0}
|
||||||
|
suffix="分"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic title="排名" value={competitivenessData?.rank || 0} suffix="位" />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Card>
|
||||||
|
<Statistic
|
||||||
|
title="超过百分比"
|
||||||
|
value={competitivenessData?.percentile || 0}
|
||||||
|
suffix="%"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
{/* 雷达图 */}
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title="雷达图" size="small">
|
||||||
|
{/* 雷达图 */}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
style={{
|
||||||
|
border: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 匹配度进度条*/}
|
||||||
|
<div style={{ padding: '0 20px', marginTop: '10px' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#000000',
|
||||||
|
marginBottom: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
职位匹配度
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 进度条容器 */}
|
||||||
|
<div className="progress-container">
|
||||||
|
{matchingDegree.map((_, index) => (
|
||||||
|
<div key={index} className={`progress-item ${getProgressClass(index)}`} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 进度条文字 */}
|
||||||
|
<div className="progress-text">
|
||||||
|
{matchingDegree.map((text, index) => (
|
||||||
|
<div key={index} style={{ width: '25%' }}>
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default listEdit;
|
export default Detail;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { FormOutlined, PlusOutlined } from '@ant-design/icons';
|
|||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import DictTag from '@/components/DictTag';
|
import DictTag from '@/components/DictTag';
|
||||||
import { exportCmsAppUserExport } from '@/services/mobileusers/list';
|
import { exportCmsAppUserExport } from '@/services/mobileusers/list';
|
||||||
import { exportCmsJobCandidates, getCmsJobIds } from '@/services/Management/list';
|
import { exportCmsJobCandidates, getCmsJobIds,getJobCompetitiveness } from '@/services/Management/list';
|
||||||
import similarityJobs from '@/utils/similarity_Job';
|
import similarityJobs from '@/utils/similarity_Job';
|
||||||
import Detail from './detail';
|
import Detail from './detail';
|
||||||
|
|
||||||
@@ -26,7 +26,6 @@ const handleExport = async (values: API.MobileUser.ListParams) => {
|
|||||||
|
|
||||||
function ManagementList() {
|
function ManagementList() {
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
|
|
||||||
const formTableRef = useRef<FormInstance>();
|
const formTableRef = useRef<FormInstance>();
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
@@ -35,14 +34,15 @@ function ManagementList() {
|
|||||||
const [experienceEnum, setExperienceEnum] = useState<any>([]);
|
const [experienceEnum, setExperienceEnum] = useState<any>([]);
|
||||||
const [areaEnum, setAreaEnum] = useState<any>([]);
|
const [areaEnum, setAreaEnum] = useState<any>([]);
|
||||||
const [sexEnum, setSexEnum] = useState<any>([]);
|
const [sexEnum, setSexEnum] = useState<any>([]);
|
||||||
const [hotEnum, setHotEnum] = useState<any>([]);
|
|
||||||
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
||||||
const [currentRow, setCurrentRow] = useState<API.MobileUser.ListRow>();
|
const [currentRow, setCurrentRow] = useState<API.MobileUser.ListRow>();
|
||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [jobInfo, setJobInfo] = useState({});
|
const [jobInfo, setJobInfo] = useState({});
|
||||||
const [matchingDegree, setMatchingDegree] = useState<any>(null);
|
const [competitivenessData, setCompetitivenessData] = useState<API.ManagementList.CompetitivenessData>({});
|
||||||
|
const [competitivenessLoading, setCompetitivenessLoading] = useState<boolean>(false);
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const id = params.id || '0';
|
const id = params.id || '0';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (jobId !== id) {
|
if (jobId !== id) {
|
||||||
setJobId(id);
|
setJobId(id);
|
||||||
@@ -58,6 +58,25 @@ function ManagementList() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取竞争力分析数据
|
||||||
|
const fetchCompetitivenessData = async (userId: string, jobId: string) => {
|
||||||
|
try {
|
||||||
|
setCompetitivenessLoading(true)
|
||||||
|
const res = await getJobCompetitiveness({ userId, jobId });
|
||||||
|
if (res.code === 200) {
|
||||||
|
setCompetitivenessLoading(false)
|
||||||
|
setCompetitivenessData(res.data);
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
setCompetitivenessLoading(false)
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
setCompetitivenessLoading(false)
|
||||||
|
console.error('获取竞争力分析数据失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDictValueEnum('education', true, true).then((data) => {
|
getDictValueEnum('education', true, true).then((data) => {
|
||||||
setEducationEnum(data);
|
setEducationEnum(data);
|
||||||
@@ -143,14 +162,11 @@ function ManagementList() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '匹配度',
|
title: '投递时间',
|
||||||
dataIndex: 'minSalary',
|
dataIndex: 'applyDate',
|
||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (_, record) => (
|
|
||||||
<>{similarityJobs.calculationMatchingDegreeJob(record).overallMatch}</>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
@@ -158,21 +174,22 @@ function ManagementList() {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
dataIndex: 'jobId',
|
dataIndex: 'jobId',
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (jobId, record) => [
|
render: (_, record) => [
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
key="edit"
|
key="edit"
|
||||||
icon={<FormOutlined />}
|
icon={<FormOutlined />}
|
||||||
hidden={!access.hasPerms('area:business:List.update')}
|
hidden={!access.hasPerms('area:business:List.update')}
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
setCurrentRow(record);
|
const data = await fetchCompetitivenessData(record.userId, jobId);
|
||||||
const val = similarityJobs.calculationMatchingDegreeJob(record);
|
if (data) {
|
||||||
setMatchingDegree(val);
|
setCompetitivenessData(data);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
查看匹配详情
|
查看竞争力分析
|
||||||
</Button>,
|
</Button>,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -182,8 +199,6 @@ function ManagementList() {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<div style={{ width: '100%', float: 'right' }}>
|
<div style={{ width: '100%', float: 'right' }}>
|
||||||
<ProTable<API.MobileUser.ListRow>
|
<ProTable<API.MobileUser.ListRow>
|
||||||
// params 是需要自带的参数
|
|
||||||
// 这个参数优先级更高,会覆盖查询表单的参数
|
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
formRef={formTableRef}
|
formRef={formTableRef}
|
||||||
rowKey="jobId"
|
rowKey="jobId"
|
||||||
@@ -191,8 +206,6 @@ function ManagementList() {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
request={(params) =>
|
request={(params) =>
|
||||||
exportCmsJobCandidates(jobId).then((res) => {
|
exportCmsJobCandidates(jobId).then((res) => {
|
||||||
// const v = similarityJobs.calculationMatchingDegreeJob(res.rows[0]);
|
|
||||||
// console.log(v);
|
|
||||||
const result = {
|
const result = {
|
||||||
data: res.rows,
|
data: res.rows,
|
||||||
total: res.total,
|
total: res.total,
|
||||||
@@ -217,14 +230,13 @@ function ManagementList() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Detail
|
<Detail
|
||||||
values={currentRow}
|
|
||||||
open={modalVisible}
|
open={modalVisible}
|
||||||
matching={matchingDegree}
|
loading={competitivenessLoading}
|
||||||
|
competitivenessData={competitivenessData}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
console.log('close');
|
|
||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
setCurrentRow(undefined);
|
setCurrentRow(undefined);
|
||||||
setMatchingDegree(null);
|
setCompetitivenessData({});
|
||||||
}}
|
}}
|
||||||
></Detail>
|
></Detail>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import {
|
|||||||
ProFormText,
|
ProFormText,
|
||||||
ProFormTextArea,
|
ProFormTextArea,
|
||||||
ProDescriptions,
|
ProDescriptions,
|
||||||
|
ProFormUploadButton,
|
||||||
|
ProFormRadio,
|
||||||
} from '@ant-design/pro-components';
|
} from '@ant-design/pro-components';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect ,useState} from 'react';
|
||||||
import { DictValueEnumObj } from '@/components/DictTag';
|
import { DictValueEnumObj } from '@/components/DictTag';
|
||||||
import { getCmsCompanyList } from '@/services/company/list';
|
import { getCmsCompanyList } from '@/services/company/list';
|
||||||
|
import { cmsfileupload } from '@/services/Management/list';
|
||||||
|
|
||||||
export type ListFormProps = {
|
export type ListFormProps = {
|
||||||
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
||||||
@@ -20,45 +23,133 @@ export type ListFormProps = {
|
|||||||
educationEnum: DictValueEnumObj;
|
educationEnum: DictValueEnumObj;
|
||||||
experienceEnum: DictValueEnumObj;
|
experienceEnum: DictValueEnumObj;
|
||||||
areaEnum: DictValueEnumObj;
|
areaEnum: DictValueEnumObj;
|
||||||
mode?: 'view' | 'edit' | 'create';
|
isExplainOptions: any;
|
||||||
};
|
mode?: 'view' | 'edit' | 'create';
|
||||||
|
|
||||||
const waitTime = (time: number = 100) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(true);
|
|
||||||
}, time);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const listEdit: React.FC<ListFormProps> = (props) => {
|
const listEdit: React.FC<ListFormProps> = (props) => {
|
||||||
const [form] = Form.useForm<API.ManagementList.Manage>();
|
const [form] = Form.useForm<API.ManagementList.Manage>();
|
||||||
const { educationEnum, experienceEnum, areaEnum } = props;
|
const [initialCompanyOptions, setInitialCompanyOptions] = useState<Array<{label: string, value: string | number}>>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { educationEnum, experienceEnum, areaEnum, isExplainOptions } = props;
|
||||||
const { mode = props.values ? 'edit' : 'create' } = props;
|
const { mode = props.values ? 'edit' : 'create' } = props;
|
||||||
|
// 预加载公司数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(props.open){
|
const preloadCompanyData = async () => {
|
||||||
form.resetFields();
|
if (props.open && props.values?.companyId && props.values?.companyName) {
|
||||||
if (props.values) {
|
setLoading(true);
|
||||||
form.setFieldsValue({
|
try {
|
||||||
...props.values,
|
// 1. 先搜索当前公司名称
|
||||||
jobLocationAreaCode: String(props.values.jobLocationAreaCode || ''),
|
const resData = await getCmsCompanyList({
|
||||||
});
|
name: props.values.companyName,
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}, [form, props.values?.jobId,props.open]);
|
let options = resData.rows.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.companyId
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 2. 如果当前公司不在搜索结果中,手动添加
|
||||||
|
const exists = options.some(opt => opt.value === props.values!.companyId);
|
||||||
|
if (!exists) {
|
||||||
|
options = [
|
||||||
|
{ label: props.values.companyName, value: props.values.companyId },
|
||||||
|
...options
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitialCompanyOptions(options);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('预加载公司失败:', error);
|
||||||
|
// 如果请求失败,至少添加当前公司
|
||||||
|
setInitialCompanyOptions([
|
||||||
|
{ label: props.values.companyName, value: props.values.companyId }
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
preloadCompanyData();
|
||||||
|
}, [props.open, props.values?.companyId, props.values?.companyName]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.open) {
|
||||||
|
form.resetFields();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (props.values) {
|
||||||
|
form.setFieldsValue({
|
||||||
|
...props.values,
|
||||||
|
jobLocationAreaCode: String(props.values.jobLocationAreaCode || ''),
|
||||||
|
isExplain: Number(props.values.isExplain) || 0,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
form.setFieldsValue({ isExplain: 0 });
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [form, props.values, props.open]);
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
props.onCancel();
|
props.onCancel();
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFinish = async (values: Record<string, any>) => {
|
const handleFinish = async (values: Record<string, any>) => {
|
||||||
props.onSubmit(values as API.ManagementList.Manage);
|
const submitValues = {
|
||||||
|
...values,
|
||||||
|
isExplain: values.isExplain ?? 0,
|
||||||
|
};
|
||||||
|
props.onSubmit(submitValues as API.ManagementList.Manage);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (_: string, value: any) => {
|
const handleChange = (_: string, value: any) => {
|
||||||
form.setFieldValue('companyName', value.label);
|
form.setFieldValue('companyName', value.label);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const customUpload = async (options: any) => {
|
||||||
|
const { file, onSuccess, onError } = options;
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
onError(new Error('文件不能为空'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await cmsfileupload(formData);
|
||||||
|
|
||||||
|
// Validate response structure
|
||||||
|
if (!response || typeof response !== 'object') {
|
||||||
|
throw new Error('上传响应格式错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
const fileUrl = typeof response.msg === 'string' ? response.msg : '';
|
||||||
|
const coverUrl = typeof response.coverUrl === 'string' ? response.coverUrl : '';
|
||||||
|
|
||||||
|
if (fileUrl) {
|
||||||
|
(form as any).setFieldValue('explainUrl', fileUrl);
|
||||||
|
}
|
||||||
|
if (coverUrl) {
|
||||||
|
(form as any).setFieldValue('cover', coverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(response);
|
||||||
|
} else {
|
||||||
|
const errorMessage = typeof response.message === 'string' ? response.message : '上传失败';
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('File upload error:', error);
|
||||||
|
const errorMessage = error instanceof Error ? error.message : '上传过程中发生未知错误';
|
||||||
|
onError(new Error(errorMessage));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (mode === 'view') {
|
if (mode === 'view') {
|
||||||
return (
|
return (
|
||||||
<ModalForm
|
<ModalForm
|
||||||
@@ -68,39 +159,47 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
modalProps={{
|
modalProps={{
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
onCancel: () => handleCancel(),
|
onCancel: () => handleCancel(),
|
||||||
footer: null,
|
footer: null,
|
||||||
}}
|
}}
|
||||||
submitter={false}
|
submitter={false}
|
||||||
>
|
>
|
||||||
<ProDescriptions<API.ManagementList.Manage>
|
<ProDescriptions<API.ManagementList.Manage> column={2} dataSource={props.values || {}}>
|
||||||
column={2}
|
|
||||||
dataSource={props.values || {}}
|
|
||||||
>
|
|
||||||
<ProDescriptions.Item dataIndex="jobTitle" label="岗位名称" />
|
<ProDescriptions.Item dataIndex="jobTitle" label="岗位名称" />
|
||||||
<ProDescriptions.Item dataIndex="companyName" label="招聘公司" />
|
<ProDescriptions.Item dataIndex="companyName" label="招聘公司" />
|
||||||
<ProDescriptions.Item dataIndex="minSalary" label="最低薪资(元/月)" />
|
<ProDescriptions.Item dataIndex="minSalary" label="最低薪资(元/月)" />
|
||||||
<ProDescriptions.Item dataIndex="maxSalary" label="最高薪资(元/月)" />
|
<ProDescriptions.Item dataIndex="maxSalary" label="最高薪资(元/月)" />
|
||||||
<ProDescriptions.Item
|
<ProDescriptions.Item dataIndex="education" label="学历要求" valueEnum={educationEnum} />
|
||||||
dataIndex="education"
|
<ProDescriptions.Item
|
||||||
label="学历要求"
|
dataIndex="experience"
|
||||||
valueEnum={educationEnum}
|
|
||||||
/>
|
|
||||||
<ProDescriptions.Item
|
|
||||||
dataIndex="experience"
|
|
||||||
label="工作经验"
|
label="工作经验"
|
||||||
valueEnum={experienceEnum}
|
valueEnum={experienceEnum}
|
||||||
/>
|
/>
|
||||||
<ProDescriptions.Item
|
<ProDescriptions.Item
|
||||||
dataIndex="jobLocationAreaCode"
|
dataIndex="jobLocationAreaCode"
|
||||||
label="工作区县"
|
label="工作区县"
|
||||||
valueEnum={areaEnum}
|
valueEnum={areaEnum}
|
||||||
/>
|
/>
|
||||||
<ProDescriptions.Item dataIndex="vacancies" label="招聘人数" />
|
<ProDescriptions.Item dataIndex="vacancies" label="招聘人数" />
|
||||||
<ProDescriptions.Item dataIndex="jobLocation" label="工作地点" />
|
<ProDescriptions.Item dataIndex="jobLocation" label="工作地点" />
|
||||||
<ProDescriptions.Item
|
<ProDescriptions.Item dataIndex="explainUrl" label="视频" />
|
||||||
dataIndex="description"
|
<ProDescriptions.Item dataIndex="cover" label="视频帧封面图" />
|
||||||
label="岗位描述"
|
<ProDescriptions.Item
|
||||||
span={2} // 跨两列显示
|
dataIndex="isExplain"
|
||||||
|
label="是否显示讲解视频"
|
||||||
|
valueType="select"
|
||||||
|
valueEnum={{
|
||||||
|
0: '关闭显示',
|
||||||
|
1: '显示视频',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProDescriptions.Item dataIndex="jobId" label="岗位编号" />
|
||||||
|
<ProDescriptions.Item dataIndex="companyId" label="公司编号" />
|
||||||
|
<ProDescriptions.Item dataIndex="isHot" label="是否热门" />
|
||||||
|
<ProDescriptions.Item dataIndex="isPublish" label="是否发布" />
|
||||||
|
<ProDescriptions.Item
|
||||||
|
dataIndex="description"
|
||||||
|
label="岗位描述"
|
||||||
|
span={2} // 跨两列显示
|
||||||
/>
|
/>
|
||||||
</ProDescriptions>
|
</ProDescriptions>
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
@@ -108,7 +207,6 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ModalForm<API.ManagementList.Manage>
|
<ModalForm<API.ManagementList.Manage>
|
||||||
|
|
||||||
title={mode === 'edit' ? '编辑岗位' : '新建岗位'}
|
title={mode === 'edit' ? '编辑岗位' : '新建岗位'}
|
||||||
form={form}
|
form={form}
|
||||||
autoFocusFirstInput
|
autoFocusFirstInput
|
||||||
@@ -119,8 +217,13 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
submitTimeout={2000}
|
submitTimeout={2000}
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
|
initialValues={{
|
||||||
|
isExplain: 0,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ProForm.Group>
|
<ProForm.Group>
|
||||||
|
<ProFormText width="md" hidden name="explainUrl" />
|
||||||
|
<ProFormText width="md" hidden name="cover" />
|
||||||
<ProFormText width="md" hidden name="jobId" />
|
<ProFormText width="md" hidden name="jobId" />
|
||||||
<ProFormText width="md" hidden name="companyName" />
|
<ProFormText width="md" hidden name="companyName" />
|
||||||
<ProFormText width="md" name="jobTitle" label="岗位名称" placeholder="请输入岗位名称" />
|
<ProFormText width="md" name="jobTitle" label="岗位名称" placeholder="请输入岗位名称" />
|
||||||
@@ -128,12 +231,39 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
showSearch
|
showSearch
|
||||||
width="md"
|
width="md"
|
||||||
name="companyId"
|
name="companyId"
|
||||||
onChange={handleChange}
|
fieldProps={{
|
||||||
request={async ({ keyWords }) => {
|
onChange: (value, option) => {
|
||||||
let resData = await getCmsCompanyList({ name: keyWords });
|
if (option && option.label) {
|
||||||
return resData.rows.map((item) => ({ label: item.name, value: item.companyId }));
|
form.setFieldValue('companyName', option.label);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loading: loading,
|
||||||
|
options: initialCompanyOptions,
|
||||||
|
defaultActiveFirstOption: false,
|
||||||
}}
|
}}
|
||||||
placeholder="请输入公司名称选择公司"
|
request={async ({ keyWords }) => {
|
||||||
|
const resData = await getCmsCompanyList({
|
||||||
|
name: keyWords,
|
||||||
|
});
|
||||||
|
const options = resData.rows.map((item) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.companyId
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 如果是编辑模式,确保当前公司被包含
|
||||||
|
if (props.values?.companyId && props.values?.companyName && keyWords) {
|
||||||
|
const exists = options.some(opt => opt.value === props.values!.companyId);
|
||||||
|
if (!exists && props.values.companyName.toLowerCase().includes(keyWords.toLowerCase())) {
|
||||||
|
options.unshift({
|
||||||
|
label: props.values.companyName,
|
||||||
|
value: props.values.companyId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}}
|
||||||
|
placeholder={loading ? "加载中..." : "请输入公司名称选择公司"}
|
||||||
rules={[{ required: true, message: '请输入公司名称选择公司!' }]}
|
rules={[{ required: true, message: '请输入公司名称选择公司!' }]}
|
||||||
label="招聘会公司"
|
label="招聘会公司"
|
||||||
/>
|
/>
|
||||||
@@ -200,6 +330,26 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
|||||||
placeholder="请输入岗位描述"
|
placeholder="请输入岗位描述"
|
||||||
/>
|
/>
|
||||||
</ProForm.Group>
|
</ProForm.Group>
|
||||||
|
<ProForm.Group>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="isExplain"
|
||||||
|
label={'是否显示讲解视频'}
|
||||||
|
colProps={{ md: 24 }}
|
||||||
|
placeholder="请选择是否显示讲解视频"
|
||||||
|
options={isExplainOptions}
|
||||||
|
/>
|
||||||
|
</ProForm.Group>
|
||||||
|
<ProForm.Group>
|
||||||
|
<ProFormUploadButton
|
||||||
|
name="video"
|
||||||
|
label="视频上传"
|
||||||
|
max={1}
|
||||||
|
fieldProps={{
|
||||||
|
customRequest: customUpload,
|
||||||
|
accept: 'video/*',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ProForm.Group>
|
||||||
</ModalForm>
|
</ModalForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -67,6 +67,17 @@ function ManagementList() {
|
|||||||
const [currentRow, setCurrentRow] = useState<API.ManagementList.Manage>();
|
const [currentRow, setCurrentRow] = useState<API.ManagementList.Manage>();
|
||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create');
|
const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create');
|
||||||
|
const isExplainOptions = [
|
||||||
|
{
|
||||||
|
label: '关闭显示',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '显示视频',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getDictValueEnum('education', true, true).then((data) => {
|
getDictValueEnum('education', true, true).then((data) => {
|
||||||
setEducationEnum(data);
|
setEducationEnum(data);
|
||||||
@@ -110,12 +121,14 @@ function ManagementList() {
|
|||||||
dataIndex: 'minSalary',
|
dataIndex: 'minSalary',
|
||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
render: (text, record) => (record.minSalary ? record.minSalary : '面议'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '最大薪资(元/月)',
|
title: '最大薪资(元/月)',
|
||||||
dataIndex: 'maxSalary',
|
dataIndex: 'maxSalary',
|
||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
render: (text, record) => (record.maxSalary ? record.maxSalary : '面议'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '单位名称',
|
title: '单位名称',
|
||||||
@@ -169,6 +182,7 @@ function ManagementList() {
|
|||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
|
render: (text, record) => (record.vacancies== -1 ? '若干' : record.vacancies),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '浏览量',
|
title: '浏览量',
|
||||||
@@ -177,6 +191,15 @@ function ManagementList() {
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '讲解视频',
|
||||||
|
dataIndex: 'isExplain',
|
||||||
|
valueType: 'text',
|
||||||
|
valueEnum: isPublishEnum,
|
||||||
|
align: 'center',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (text, record) => (record.cover ? '有' : '无'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
@@ -220,6 +243,7 @@ function ManagementList() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
setCurrentRow(record);
|
setCurrentRow(record);
|
||||||
|
setMode('edit');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
@@ -254,6 +278,7 @@ function ManagementList() {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div style={{ width: '100%', float: 'right' }}>
|
<div style={{ width: '100%', float: 'right' }}>
|
||||||
@@ -332,6 +357,7 @@ function ManagementList() {
|
|||||||
setModalVisible(false);
|
setModalVisible(false);
|
||||||
setCurrentRow(undefined);
|
setCurrentRow(undefined);
|
||||||
}}
|
}}
|
||||||
|
isExplainOptions={isExplainOptions}
|
||||||
educationEnum={educationEnum}
|
educationEnum={educationEnum}
|
||||||
experienceEnum={experienceEnum}
|
experienceEnum={experienceEnum}
|
||||||
areaEnum={areaEnum}
|
areaEnum={areaEnum}
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ import { DictValueEnumObj } from '@/components/DictTag';
|
|||||||
*
|
*
|
||||||
* @author whiteshader@163.com
|
* @author whiteshader@163.com
|
||||||
* @datetime 2023/02/06
|
* @datetime 2023/02/06
|
||||||
*
|
*
|
||||||
* */
|
* */
|
||||||
|
|
||||||
|
|
||||||
export type UserFormData = Record<string, unknown> & Partial<API.System.User>;
|
export type UserFormData = Record<string, unknown> & Partial<API.System.User>;
|
||||||
|
|
||||||
export type UserFormProps = {
|
export type UserFormProps = {
|
||||||
@@ -39,7 +38,7 @@ export type UserFormProps = {
|
|||||||
const UserForm: React.FC<UserFormProps> = (props) => {
|
const UserForm: React.FC<UserFormProps> = (props) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const userId = Form.useWatch('userId', form);
|
const userId = Form.useWatch('userId', form);
|
||||||
const { sexOptions, statusOptions, } = props;
|
const { sexOptions, statusOptions } = props;
|
||||||
const { roles, posts, depts } = props;
|
const { roles, posts, depts } = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -71,6 +70,9 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
props.onCancel();
|
props.onCancel();
|
||||||
};
|
};
|
||||||
const handleFinish = async (values: Record<string, any>) => {
|
const handleFinish = async (values: Record<string, any>) => {
|
||||||
|
if (props.values.userId) {
|
||||||
|
values.userId = props.values.userId;
|
||||||
|
}
|
||||||
props.onSubmit(values as UserFormData);
|
props.onSubmit(values as UserFormData);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,7 +93,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
form={form}
|
form={form}
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
submitter={false}
|
submitter={false}
|
||||||
onFinish={handleFinish}>
|
onFinish={handleFinish}
|
||||||
|
>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="nickName"
|
name="nickName"
|
||||||
label={intl.formatMessage({
|
label={intl.formatMessage({
|
||||||
@@ -103,9 +106,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: (
|
message: <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />,
|
||||||
<FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -123,9 +124,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: (
|
message: <FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />,
|
||||||
<FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -140,9 +139,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
required: false,
|
||||||
message: (
|
message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />,
|
||||||
<FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -157,9 +154,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
required: false,
|
||||||
message: (
|
message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />,
|
||||||
<FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -207,9 +202,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
required: false,
|
||||||
message: (
|
message: <FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />,
|
||||||
<FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -226,9 +219,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
required: false,
|
||||||
message: (
|
message: <FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />,
|
||||||
<FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,11 +1,32 @@
|
|||||||
|
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
|
import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
|
||||||
import { Card, Col, Dropdown, FormInstance, Row, Space, Switch } from 'antd';
|
import { Card, Col, Dropdown, FormInstance, Row, Space, Switch } from 'antd';
|
||||||
import { Button, message, Modal } from 'antd';
|
import { Button, message, Modal } from 'antd';
|
||||||
import { ActionType, FooterToolbar, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
|
import {
|
||||||
import { PlusOutlined, DeleteOutlined, ExclamationCircleOutlined, DownOutlined, EditOutlined } from '@ant-design/icons';
|
ActionType,
|
||||||
import { getUserList, removeUser, addUser, updateUser, exportUser, getUser, changeUserStatus, updateAuthRole, resetUserPwd } from '@/services/system/user';
|
FooterToolbar,
|
||||||
|
PageContainer,
|
||||||
|
ProColumns,
|
||||||
|
ProTable,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
getUserList,
|
||||||
|
removeUser,
|
||||||
|
addUser,
|
||||||
|
updateUser,
|
||||||
|
exportUser,
|
||||||
|
getUser,
|
||||||
|
changeUserStatus,
|
||||||
|
updateAuthRole,
|
||||||
|
resetUserPwd,
|
||||||
|
} from '@/services/system/user';
|
||||||
import UpdateForm from './edit';
|
import UpdateForm from './edit';
|
||||||
import { getDictValueEnum } from '@/services/system/dict';
|
import { getDictValueEnum } from '@/services/system/dict';
|
||||||
import { DataNode } from 'antd/es/tree';
|
import { DataNode } from 'antd/es/tree';
|
||||||
@@ -33,10 +54,17 @@ const { confirm } = Modal;
|
|||||||
const handleAdd = async (fields: API.System.User) => {
|
const handleAdd = async (fields: API.System.User) => {
|
||||||
const hide = message.loading('正在添加');
|
const hide = message.loading('正在添加');
|
||||||
try {
|
try {
|
||||||
await addUser({ ...fields });
|
const rested = await addUser({ ...fields });
|
||||||
hide();
|
console.log(rested);
|
||||||
message.success('添加成功');
|
if (rested.code === 200) {
|
||||||
return true;
|
hide();
|
||||||
|
message.success('添加成功');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
message.error('添加失败请重试!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hide();
|
hide();
|
||||||
message.error('添加失败请重试!');
|
message.error('添加失败请重试!');
|
||||||
@@ -52,10 +80,17 @@ const handleAdd = async (fields: API.System.User) => {
|
|||||||
const handleUpdate = async (fields: API.System.User) => {
|
const handleUpdate = async (fields: API.System.User) => {
|
||||||
const hide = message.loading('正在配置');
|
const hide = message.loading('正在配置');
|
||||||
try {
|
try {
|
||||||
await updateUser(fields);
|
const rested = await updateUser(fields);
|
||||||
hide();
|
console.log(rested);
|
||||||
message.success('配置成功');
|
if (rested.code === 200) {
|
||||||
return true;
|
hide();
|
||||||
|
message.success('配置成功');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
message.error('配置失败请重试!');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hide();
|
hide();
|
||||||
message.error('配置失败请重试!');
|
message.error('配置失败请重试!');
|
||||||
@@ -156,12 +191,12 @@ const UserTableList: React.FC = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const showChangeStatusConfirm = (record: API.System.User) => {
|
const showChangeStatusConfirm = (record: API.System.User) => {
|
||||||
let text = record.status === "1" ? "启用" : "停用";
|
let text = record.status === '1' ? '启用' : '停用';
|
||||||
const newStatus = record.status === '0' ? '1' : '0';
|
const newStatus = record.status === '0' ? '1' : '0';
|
||||||
confirm({
|
confirm({
|
||||||
title: `确认要${text}${record.userName}用户吗?`,
|
title: `确认要${text}${record.userName}用户吗?`,
|
||||||
onOk() {
|
onOk() {
|
||||||
changeUserStatus(record.userId, newStatus).then(resp => {
|
changeUserStatus(record.userId, newStatus).then((resp) => {
|
||||||
if (resp.code === 200) {
|
if (resp.code === 200) {
|
||||||
messageApi.open({
|
messageApi.open({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
@@ -221,7 +256,7 @@ const UserTableList: React.FC = () => {
|
|||||||
title: <FormattedMessage id="system.user.dept_name" defaultMessage="部门" />,
|
title: <FormattedMessage id="system.user.dept_name" defaultMessage="部门" />,
|
||||||
dataIndex: ['dept', 'deptName'],
|
dataIndex: ['dept', 'deptName'],
|
||||||
valueType: 'text',
|
valueType: 'text',
|
||||||
hideInSearch: true
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
|
title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
|
||||||
@@ -241,7 +276,8 @@ const UserTableList: React.FC = () => {
|
|||||||
unCheckedChildren="停用"
|
unCheckedChildren="停用"
|
||||||
defaultChecked
|
defaultChecked
|
||||||
onClick={() => showChangeStatusConfirm(record)}
|
onClick={() => showChangeStatusConfirm(record)}
|
||||||
/>)
|
/>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -297,7 +333,9 @@ const UserTableList: React.FC = () => {
|
|||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: <FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />,
|
label: (
|
||||||
|
<FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />
|
||||||
|
),
|
||||||
key: 'reset',
|
key: 'reset',
|
||||||
disabled: !access.hasPerms('system:user:edit.tsx'),
|
disabled: !access.hasPerms('system:user:edit.tsx'),
|
||||||
},
|
},
|
||||||
@@ -311,13 +349,12 @@ const UserTableList: React.FC = () => {
|
|||||||
if (key === 'reset') {
|
if (key === 'reset') {
|
||||||
setResetPwdModalVisible(true);
|
setResetPwdModalVisible(true);
|
||||||
setCurrentRow(record);
|
setCurrentRow(record);
|
||||||
}
|
} else if (key === 'authRole') {
|
||||||
else if (key === 'authRole') {
|
|
||||||
fetchUserInfo(record.userId);
|
fetchUserInfo(record.userId);
|
||||||
setAuthRoleModalVisible(true);
|
setAuthRoleModalVisible(true);
|
||||||
setCurrentRow(record);
|
setCurrentRow(record);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a onClick={(e) => e.preventDefault()}>
|
<a onClick={(e) => e.preventDefault()}>
|
||||||
@@ -369,7 +406,7 @@ const UserTableList: React.FC = () => {
|
|||||||
const treeData = await getDeptTree({});
|
const treeData = await getDeptTree({});
|
||||||
setDeptTree(treeData);
|
setDeptTree(treeData);
|
||||||
|
|
||||||
const postResp = await getPostList()
|
const postResp = await getPostList();
|
||||||
if (postResp.code === 200) {
|
if (postResp.code === 200) {
|
||||||
setPostList(
|
setPostList(
|
||||||
postResp.rows.map((item: any) => {
|
postResp.rows.map((item: any) => {
|
||||||
@@ -381,7 +418,7 @@ const UserTableList: React.FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roleResp = await getRoleList()
|
const roleResp = await getRoleList();
|
||||||
if (roleResp.code === 200) {
|
if (roleResp.code === 200) {
|
||||||
setRoleList(
|
setRoleList(
|
||||||
roleResp.rows.map((item: any) => {
|
roleResp.rows.map((item: any) => {
|
||||||
@@ -396,7 +433,8 @@ const UserTableList: React.FC = () => {
|
|||||||
setModalVisible(true);
|
setModalVisible(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
|
<PlusOutlined />{' '}
|
||||||
|
<FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
|
||||||
</Button>,
|
</Button>,
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -415,7 +453,7 @@ const UserTableList: React.FC = () => {
|
|||||||
actionRef.current?.reloadAndRest?.();
|
actionRef.current?.reloadAndRest?.();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCancel() { },
|
onCancel() {},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -435,14 +473,17 @@ const UserTableList: React.FC = () => {
|
|||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
request={(params) =>
|
request={(params) =>
|
||||||
getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then((res) => {
|
getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then(
|
||||||
const result = {
|
(res) => {
|
||||||
data: res.rows,
|
console.log(res);
|
||||||
total: res.total,
|
const result = {
|
||||||
success: true,
|
data: res.rows,
|
||||||
};
|
total: res.total,
|
||||||
return result;
|
success: true,
|
||||||
})
|
};
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
@@ -490,6 +531,7 @@ const UserTableList: React.FC = () => {
|
|||||||
<UpdateForm
|
<UpdateForm
|
||||||
onSubmit={async (values) => {
|
onSubmit={async (values) => {
|
||||||
let success = false;
|
let success = false;
|
||||||
|
console.log({ ...values });
|
||||||
if (values.userId) {
|
if (values.userId) {
|
||||||
success = await handleUpdate({ ...values } as API.System.User);
|
success = await handleUpdate({ ...values } as API.System.User);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ const Login: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
submitter={type === 'scanQode' ? false : true}
|
submitter={type === 'scanQode' ? false : true}
|
||||||
// logo={<img alt="logo" src={logoImg} />}
|
// logo={<img alt="logo" src={logoImg} />}
|
||||||
title="青岛智慧就业服务系统"
|
title="石河子智慧就业服务系统"
|
||||||
// subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
|
// subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
autoLogin: true,
|
autoLogin: true,
|
||||||
|
|||||||
@@ -57,3 +57,17 @@ export async function getJobTrend(params) {
|
|||||||
params: params
|
params: params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cmsfileupload(params) {
|
||||||
|
return request(`/api/cms/file/upload`, {
|
||||||
|
method: 'POST',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getJobCompetitiveness(params: { userId: number | string; jobId: string }) {
|
||||||
|
return request<API.ManagementList.CompetitivenessResult>(`/api/cms/job/competitiveness`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,159 +1,166 @@
|
|||||||
import { createIcon } from '@/utils/IconUtil';
|
import { createIcon } from '@/utils/IconUtil';
|
||||||
import { MenuDataItem } from '@ant-design/pro-components';
|
import { MenuDataItem } from '@ant-design/pro-components';
|
||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
import React, { lazy } from 'react';
|
import React, { lazy } from 'react';
|
||||||
|
|
||||||
|
|
||||||
let remoteMenu: any = null;
|
let remoteMenu: any = null;
|
||||||
|
|
||||||
export function getRemoteMenu() {
|
export function getRemoteMenu() {
|
||||||
return remoteMenu;
|
return remoteMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setRemoteMenu(data: any) {
|
export function setRemoteMenu(data: any) {
|
||||||
remoteMenu = data;
|
remoteMenu = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function patchRouteItems(route: any, menu: any, parentPath: string) {
|
function patchRouteItems(route: any, menu: any, parentPath: string) {
|
||||||
for (const menuItem of menu) {
|
for (const menuItem of menu) {
|
||||||
if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') {
|
console.log(menuItem, 'menuItem')
|
||||||
if (menuItem.routes) {
|
if (menuItem.component === 'Layout' || menuItem.component === 'ParentView') {
|
||||||
let hasItem = false;
|
if (menuItem.routes) {
|
||||||
let newItem = null;
|
let hasItem = false;
|
||||||
for (const routeChild of route.routes) {
|
let newItem = null;
|
||||||
if (routeChild.path === menuItem.path) {
|
for (const routeChild of route.routes) {
|
||||||
hasItem = true;
|
if (routeChild.path === menuItem.path) {
|
||||||
newItem = routeChild;
|
hasItem = true;
|
||||||
}
|
newItem = routeChild;
|
||||||
}
|
}
|
||||||
if (!hasItem) {
|
}
|
||||||
newItem = {
|
if (!hasItem) {
|
||||||
path: menuItem.path,
|
newItem = {
|
||||||
routes: [],
|
path: menuItem.path,
|
||||||
children: []
|
routes: [],
|
||||||
}
|
children: []
|
||||||
route.routes.push(newItem)
|
}
|
||||||
}
|
route.routes.push(newItem)
|
||||||
patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/');
|
}
|
||||||
}
|
patchRouteItems(newItem, menuItem.routes, parentPath + menuItem.path + '/');
|
||||||
} else {
|
}
|
||||||
const names: string[] = menuItem.component.split('/');
|
} else {
|
||||||
let path = '';
|
const names: string[] = menuItem.component && menuItem.component.split('/');
|
||||||
names.forEach(name => {
|
console.log(names, 'names')
|
||||||
if (path.length > 0) {
|
let path = '';
|
||||||
path += '/';
|
names && names.forEach(name => {
|
||||||
}
|
if (path.length > 0) {
|
||||||
if (name !== 'index') {
|
path += '/';
|
||||||
path += name.at(0)?.toUpperCase() + name.substr(1);
|
}
|
||||||
} else {
|
if (name !== 'index') {
|
||||||
path += name;
|
path += name.at(0)?.toUpperCase() + name.substr(1);
|
||||||
}
|
} else {
|
||||||
})
|
path += name;
|
||||||
if (!path.endsWith('.tsx')) {
|
}
|
||||||
path += '.tsx'
|
})
|
||||||
}
|
if (!path.endsWith('.tsx')) {
|
||||||
if (route.routes === undefined) {
|
path += '.tsx'
|
||||||
route.routes = [];
|
}
|
||||||
}
|
if (route.routes === undefined) {
|
||||||
if (route.children === undefined) {
|
route.routes = [];
|
||||||
route.children = [];
|
}
|
||||||
}
|
if (route.children === undefined) {
|
||||||
const newRoute = {
|
route.children = [];
|
||||||
element: React.createElement(lazy(() => import('@/pages/' + path))),
|
}
|
||||||
path: parentPath + menuItem.path,
|
const newRoute = {
|
||||||
}
|
element: React.createElement(lazy(() => import('@/pages/' + path))),
|
||||||
route.children.push(newRoute);
|
path: parentPath + menuItem.path,
|
||||||
route.routes.push(newRoute);
|
}
|
||||||
}
|
route.children.push(newRoute);
|
||||||
}
|
route.routes.push(newRoute);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
export function patchRouteWithRemoteMenus(routes: any) {
|
}
|
||||||
if (remoteMenu === null) { return; }
|
|
||||||
let proLayout = null;
|
export function patchRouteWithRemoteMenus(routes: any) {
|
||||||
for (const routeItem of routes) {
|
if (remoteMenu === null) { return; }
|
||||||
if (routeItem.id === 'ant-design-pro-layout') {
|
let proLayout = null;
|
||||||
proLayout = routeItem;
|
for (const routeItem of routes) {
|
||||||
break;
|
if (routeItem.id === 'ant-design-pro-layout') {
|
||||||
}
|
proLayout = routeItem;
|
||||||
}
|
break;
|
||||||
patchRouteItems(proLayout, remoteMenu, '');
|
}
|
||||||
}
|
}
|
||||||
|
patchRouteItems(proLayout, remoteMenu, '');
|
||||||
/** 获取当前的用户 GET /api/getUserInfo */
|
}
|
||||||
export async function getUserInfo(options?: Record<string, any>) {
|
|
||||||
return request<API.UserInfoResult>('/api/getInfo', {
|
/** 获取当前的用户 GET /api/getUserInfo */
|
||||||
method: 'GET',
|
export async function getUserInfo(options?: Record<string, any>) {
|
||||||
...(options || {}),
|
return request<API.UserInfoResult>('/api/getInfo', {
|
||||||
});
|
method: 'GET',
|
||||||
}
|
...(options || {}),
|
||||||
|
});
|
||||||
// 刷新方法
|
}
|
||||||
export async function refreshToken() {
|
|
||||||
return request('/api/auth/refresh', {
|
// 刷新方法
|
||||||
method: 'post'
|
export async function refreshToken() {
|
||||||
})
|
return request('/api/auth/refresh', {
|
||||||
}
|
method: 'post'
|
||||||
|
})
|
||||||
export async function getRouters(): Promise<any> {
|
}
|
||||||
return request('/api/getRouters');
|
|
||||||
}
|
export async function getRouters(): Promise<any> {
|
||||||
|
return request('/api/getRouters');
|
||||||
export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
|
}
|
||||||
return childrens.map((item: API.RoutersMenuItem) => {
|
|
||||||
return {
|
export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
|
||||||
path: item.path,
|
// childrens = childrens.filter((item) => item.meta !== undefined);
|
||||||
icon: createIcon(item.meta.icon),
|
return childrens.map((item: API.RoutersMenuItem) => {
|
||||||
// icon: item.meta.icon,
|
if (!item.meta && item.children?.length) {
|
||||||
name: item.meta.title,
|
item = item.children[0]
|
||||||
routes: item.children ? convertCompatRouters(item.children) : undefined,
|
}
|
||||||
hideChildrenInMenu: item.hidden,
|
return {
|
||||||
hideInMenu: item.hidden,
|
path: item.path,
|
||||||
component: item.component,
|
icon: item.meta && createIcon(item.meta.icon),
|
||||||
authority: item.perms,
|
name: item.meta && item.meta.title,
|
||||||
};
|
routes: item.children ? convertCompatRouters(item.children) : undefined,
|
||||||
});
|
hideChildrenInMenu: item.hidden,
|
||||||
}
|
hideInMenu: item.hidden,
|
||||||
|
component: item.component,
|
||||||
export async function getRoutersInfo(): Promise<MenuDataItem[]> {
|
authority: item.perms,
|
||||||
return getRouters().then((res) => {
|
};
|
||||||
if (res.code === 200) {
|
});
|
||||||
return convertCompatRouters(res.data);
|
}
|
||||||
} else {
|
|
||||||
return [];
|
export async function getRoutersInfo(): Promise<MenuDataItem[]> {
|
||||||
}
|
return getRouters().then((res) => {
|
||||||
});
|
if (res.code === 200) {
|
||||||
}
|
console.log(res.data)
|
||||||
|
console.log(convertCompatRouters(res.data))
|
||||||
export function getMatchMenuItem(
|
return convertCompatRouters(res.data);
|
||||||
path: string,
|
} else {
|
||||||
menuData: MenuDataItem[] | undefined,
|
return [];
|
||||||
): MenuDataItem[] {
|
}
|
||||||
if (!menuData) return [];
|
});
|
||||||
let items: MenuDataItem[] = [];
|
}
|
||||||
menuData.forEach((item) => {
|
|
||||||
if (item.path) {
|
export function getMatchMenuItem(
|
||||||
if (item.path === path) {
|
path: string,
|
||||||
items.push(item);
|
menuData: MenuDataItem[] | undefined,
|
||||||
return;
|
): MenuDataItem[] {
|
||||||
}
|
if (!menuData) return [];
|
||||||
if (path.length >= item.path?.length) {
|
let items: MenuDataItem[] = [];
|
||||||
const exp = `${item.path}/*`;
|
menuData.forEach((item) => {
|
||||||
if (path.match(exp)) {
|
if (item.path) {
|
||||||
if (item.routes) {
|
if (item.path === path) {
|
||||||
const subpath = path.substr(item.path.length + 1);
|
items.push(item);
|
||||||
const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes);
|
return;
|
||||||
items = items.concat(subItem);
|
}
|
||||||
} else {
|
if (path.length >= item.path?.length) {
|
||||||
const paths = path.split('/');
|
const exp = `${item.path}/*`;
|
||||||
if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
|
if (path.match(exp)) {
|
||||||
items.push(item);
|
if (item.routes) {
|
||||||
}
|
const subpath = path.substr(item.path.length + 1);
|
||||||
}
|
const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes);
|
||||||
}
|
items = items.concat(subItem);
|
||||||
}
|
} else {
|
||||||
}
|
const paths = path.split('/');
|
||||||
});
|
if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') {
|
||||||
return items;
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|||||||
49
src/types/CaseManagementAndTracking/CaseManagement.d.ts
vendored
Normal file
49
src/types/CaseManagementAndTracking/CaseManagement.d.ts
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
declare namespace API.StorageDetection {
|
||||||
|
export interface StorageDetailItem {
|
||||||
|
detailId?: string;
|
||||||
|
websiteId?: string;
|
||||||
|
websiteName?: string;
|
||||||
|
successNumber?: string;
|
||||||
|
failedNumber?: string;
|
||||||
|
storageDetail?: string;
|
||||||
|
storageTime?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageItem {
|
||||||
|
detectionId?: string;
|
||||||
|
storageDate?: string;
|
||||||
|
storageNumber?: string;
|
||||||
|
storageResult?: string;
|
||||||
|
storageDetail?: string;
|
||||||
|
websiteId?: string;
|
||||||
|
websiteName?: string;
|
||||||
|
details?: StorageDetailItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddParams {
|
||||||
|
storageDate?: string;
|
||||||
|
storageNumber?: string;
|
||||||
|
storageResult?: string;
|
||||||
|
storageDetail?: string;
|
||||||
|
websiteName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListParams {
|
||||||
|
storageDate?: string;
|
||||||
|
pageSize?: number;
|
||||||
|
current?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageIdResult {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: StorageItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StoragePageResult {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
total: number;
|
||||||
|
rows: Array<StorageItem>;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/types/Management/list.d.ts
vendored
28
src/types/Management/list.d.ts
vendored
@@ -7,6 +7,7 @@ declare namespace API.ManagementList {
|
|||||||
experience?: string;
|
experience?: string;
|
||||||
isApply?: number;
|
isApply?: number;
|
||||||
isCollection?: number;
|
isCollection?: number;
|
||||||
|
isExplain?: number;
|
||||||
isHot?: number;
|
isHot?: number;
|
||||||
jobId?: number;
|
jobId?: number;
|
||||||
jobLocation?: string;
|
jobLocation?: string;
|
||||||
@@ -31,6 +32,7 @@ declare namespace API.ManagementList {
|
|||||||
experience?: string;
|
experience?: string;
|
||||||
isApply?: number;
|
isApply?: number;
|
||||||
isCollection?: number;
|
isCollection?: number;
|
||||||
|
isExplain?: number;
|
||||||
isHot?: number;
|
isHot?: number;
|
||||||
jobId?: number;
|
jobId?: number;
|
||||||
jobLocation?: string;
|
jobLocation?: string;
|
||||||
@@ -70,3 +72,29 @@ declare namespace API.ManagementList {
|
|||||||
rows: Array<Manage>;
|
rows: Array<Manage>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare namespace API.ManagementList {
|
||||||
|
// 添加竞争力分析接口返回类型
|
||||||
|
export interface CompetitivenessData {
|
||||||
|
totalApplicants?: number;
|
||||||
|
matchScore?: number;
|
||||||
|
rank?: number;
|
||||||
|
percentile?: number;
|
||||||
|
radarChart?: RadarChart;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RadarChart {
|
||||||
|
skill?: number;
|
||||||
|
experience?: number;
|
||||||
|
education?: number;
|
||||||
|
salary?: number;
|
||||||
|
age?: number;
|
||||||
|
location?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompetitivenessResult {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
data: CompetitivenessData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user