Compare commits
10 Commits
0e66061b40
...
2a930bf682
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a930bf682 | ||
|
|
e7ad0c1567 | ||
|
|
5b6393a37d | ||
|
|
e034f117e8 | ||
|
|
49f48b132d | ||
|
|
388dc6c9fa | ||
|
|
0fef7b57a8 | ||
|
|
16f456d2f2 | ||
|
|
6007a69e8a | ||
|
|
4b0c7033e2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ _roadhog-api-doc
|
||||
|
||||
# production
|
||||
/dist
|
||||
/shihezi
|
||||
/qingdao
|
||||
|
||||
# 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
|
||||
|
||||
@@ -125,7 +125,7 @@ export default defineConfig({
|
||||
*/
|
||||
headScripts: [
|
||||
// 解决首次加载时白屏的问题
|
||||
{ src: '/qingdao/scripts/loading.js', async: true },
|
||||
{ src: '/shihezi/scripts/loading.js', async: true },
|
||||
],
|
||||
//================ pro 插件配置 =================
|
||||
presets: ['umi-presets-pro'],
|
||||
@@ -151,9 +151,9 @@ export default defineConfig({
|
||||
mfsu: {
|
||||
strategy: 'normal',
|
||||
},
|
||||
outputPath: 'qingdao',
|
||||
base: '/qingdao/',
|
||||
publicPath: '/qingdao/',
|
||||
outputPath: 'shihezi',
|
||||
base: '/shihezi/',
|
||||
publicPath: '/shihezi/',
|
||||
esbuildMinifyIIFE: true,
|
||||
requestRecord: {},
|
||||
});
|
||||
|
||||
BIN
shihezi.zip
Normal file
BIN
shihezi.zip
Normal file
Binary file not shown.
@@ -29,11 +29,11 @@ const loginOut = async () => {
|
||||
const redirect = urlParams.get('redirect');
|
||||
// Note: There may be security issues, please note
|
||||
console.log('redirect', window.location.pathname, redirect);
|
||||
if (window.location.pathname !== '/qingdao/user/login' && !redirect) {
|
||||
if (window.location.pathname !== '/shihezi/user/login' && !redirect) {
|
||||
history.replace({
|
||||
pathname: '/user/login',
|
||||
search: stringify({
|
||||
redirect: pathname.replace('/qingdao', '') + search,
|
||||
redirect: pathname.replace('/shihezi', '') + search,
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -218,7 +218,7 @@ export async function render(oldRender: () => void) {
|
||||
const checkRegion = 5 * 60 * 1000;
|
||||
export const request = {
|
||||
...errorConfig,
|
||||
baseURL: process.env.NODE_ENV === 'development' ? '' : 'https://qd.zhaopinzao8dian.com/api',
|
||||
baseURL: process.env.NODE_ENV === 'development' ? '' : 'http://36.105.163.21:30081/rgpp/api',
|
||||
// baseURL: 'http://39.98.44.136:8080',
|
||||
// baseURL:
|
||||
// process.env.NODE_ENV === 'development'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { GithubOutlined } from '@ant-design/icons';
|
||||
import { DefaultFooter } from '@ant-design/pro-components';
|
||||
import React from 'react';
|
||||
import {getYear} from '@/utils/tools'
|
||||
import { getYear } from '@/utils/tools';
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<DefaultFooter
|
||||
@@ -13,12 +13,13 @@ const Footer: React.FC = () => {
|
||||
{
|
||||
key: '青岛政务网',
|
||||
title: '青岛政务网',
|
||||
href: 'http://www.qingdao.gov.cn/',
|
||||
href: 'http://www.shihezi.gov.cn/',
|
||||
blankTarget: true,
|
||||
},{
|
||||
},
|
||||
{
|
||||
key: '青岛市人力资源和社会保障局',
|
||||
title: '青岛市人力资源和社会保障局',
|
||||
href: 'https://hrss.qingdao.gov.cn/',
|
||||
href: 'https://hrss.shihezi.gov.cn/',
|
||||
blankTarget: true,
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -53,12 +53,12 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, childre
|
||||
/** 此方法会跳转到 redirect 参数所在的位置 */
|
||||
const redirect = urlParams.get('redirect');
|
||||
// Note: There may be security issues, please note
|
||||
console.log('redirect', window.location.pathname, redirect)
|
||||
if (window.location.pathname !== '/qingdao/user/login' && !redirect) {
|
||||
console.log('redirect', window.location.pathname, redirect);
|
||||
if (window.location.pathname !== '/shihezi/user/login' && !redirect) {
|
||||
history.replace({
|
||||
pathname: '/user/login',
|
||||
search: stringify({
|
||||
redirect: pathname.replace('/qingdao', '') + search,
|
||||
redirect: pathname.replace('/shihezi', '') + search,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@/services/company/list';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import DictTag from '@/components/DictTag';
|
||||
import { getCmsIndustryTreeList } from '@/services/classify/industry';
|
||||
|
||||
// 详情查看组件
|
||||
const CompanyDetailModal = ({
|
||||
@@ -20,11 +21,13 @@ const CompanyDetailModal = ({
|
||||
onCancel,
|
||||
record,
|
||||
scaleEnum,
|
||||
industryEnum,
|
||||
}: {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
record?: API.CompanyList.Company;
|
||||
scaleEnum: Record<string, any>;
|
||||
industryEnum: Record<string, any>;
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
@@ -40,7 +43,9 @@ const CompanyDetailModal = ({
|
||||
>
|
||||
<Descriptions column={1} bordered>
|
||||
<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="公司规模">
|
||||
<DictTag enums={scaleEnum} value={record?.scale} />
|
||||
</Descriptions.Item>
|
||||
@@ -60,6 +65,7 @@ function ManagementList() {
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
const [detailVisible, setDetailVisible] = useState<boolean>(false);
|
||||
const [scaleEnum, setScaleEnum] = useState<Record<string, any>>({});
|
||||
const [industryEnum, setIndustryEnum] = useState<Record<string, any>>({});
|
||||
|
||||
const handleRemoveOne = async (jobId: string) => {
|
||||
const hide = message.loading('正在删除');
|
||||
@@ -96,8 +102,28 @@ function ManagementList() {
|
||||
|
||||
useEffect(() => {
|
||||
getDictValueEnum('scale', true, true).then((data) => {
|
||||
console.log(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>[] = [
|
||||
@@ -272,6 +298,7 @@ function ManagementList() {
|
||||
}}
|
||||
record={currentRow}
|
||||
scaleEnum={scaleEnum}
|
||||
industryEnum={industryEnum}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -1,74 +1,333 @@
|
||||
import { Modal } from 'antd';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import { Modal, Row, Col, Card, Statistic } from 'antd';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export type ListFormProps = {
|
||||
onClose: (flag?: boolean, formVals?: unknown) => void;
|
||||
open: boolean;
|
||||
values?: Partial<API.MobileUser.ListRow>;
|
||||
matching?: any;
|
||||
loading: boolean;
|
||||
competitivenessData?: API.ManagementList.CompetitivenessData;
|
||||
};
|
||||
|
||||
const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
const { open, values, matching } = props;
|
||||
const Detail: React.FC<ListFormProps> = (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(() => {
|
||||
console.log(matching);
|
||||
if (matching && open) {
|
||||
const myCharts = echarts.init(mapRef.current);
|
||||
const { educationMatch, maxSimilarity, salaryMatch, areaMatch } = matching;
|
||||
// educationMatch
|
||||
// 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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (competitivenessData?.matchScore !== undefined) {
|
||||
// 计算匹配度
|
||||
const score = competitivenessData.matchScore;
|
||||
const step = score * 0.04;
|
||||
setCurrentStep(step);
|
||||
}
|
||||
}, [matching]);
|
||||
}, [competitivenessData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && competitivenessData && canvasRef.current) {
|
||||
// 初始化Canvas
|
||||
const canvas = canvasRef.current;
|
||||
canvas.width = 400;
|
||||
canvas.height = 320;
|
||||
drawRadarChart();
|
||||
}
|
||||
}, [open, competitivenessData]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="匹配详情"
|
||||
width={400}
|
||||
open={props.open}
|
||||
footer={''}
|
||||
title="竞争力分析"
|
||||
width={700}
|
||||
open={open}
|
||||
footer={null}
|
||||
onCancel={() => props.onClose()}
|
||||
maskClosable={true}
|
||||
loading={loading}
|
||||
styles={{
|
||||
body: {
|
||||
minHeight: '600px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: '300px', height: '300px' }} ref={mapRef}></div>
|
||||
</div>
|
||||
<Row gutter={[16, 16]}>
|
||||
{/* 统计数据 */}
|
||||
<Col span={24}>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic title="总申请人数" value={competitivenessData?.totalApplicants || 0} />
|
||||
</Card>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
export default listEdit;
|
||||
export default Detail;
|
||||
|
||||
@@ -6,7 +6,7 @@ import { FormOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { getDictValueEnum } from '@/services/system/dict';
|
||||
import DictTag from '@/components/DictTag';
|
||||
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 Detail from './detail';
|
||||
|
||||
@@ -26,7 +26,6 @@ const handleExport = async (values: API.MobileUser.ListParams) => {
|
||||
|
||||
function ManagementList() {
|
||||
const access = useAccess();
|
||||
|
||||
const formTableRef = useRef<FormInstance>();
|
||||
const actionRef = useRef<ActionType>();
|
||||
|
||||
@@ -35,14 +34,15 @@ function ManagementList() {
|
||||
const [experienceEnum, setExperienceEnum] = useState<any>([]);
|
||||
const [areaEnum, setAreaEnum] = useState<any>([]);
|
||||
const [sexEnum, setSexEnum] = useState<any>([]);
|
||||
const [hotEnum, setHotEnum] = useState<any>([]);
|
||||
const [politicalEnum, setPoliticalEnum] = useState<any>([]);
|
||||
const [currentRow, setCurrentRow] = useState<API.MobileUser.ListRow>();
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
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 id = params.id || '0';
|
||||
|
||||
useEffect(() => {
|
||||
if (jobId !== 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(() => {
|
||||
getDictValueEnum('education', true, true).then((data) => {
|
||||
setEducationEnum(data);
|
||||
@@ -143,14 +162,11 @@ function ManagementList() {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '匹配度',
|
||||
dataIndex: 'minSalary',
|
||||
title: '投递时间',
|
||||
dataIndex: 'applyDate',
|
||||
valueType: 'text',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<>{similarityJobs.calculationMatchingDegreeJob(record).overallMatch}</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -158,21 +174,22 @@ function ManagementList() {
|
||||
align: 'center',
|
||||
dataIndex: 'jobId',
|
||||
width: 200,
|
||||
render: (jobId, record) => [
|
||||
render: (_, record) => [
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="edit"
|
||||
icon={<FormOutlined />}
|
||||
hidden={!access.hasPerms('area:business:List.update')}
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
setModalVisible(true);
|
||||
setCurrentRow(record);
|
||||
const val = similarityJobs.calculationMatchingDegreeJob(record);
|
||||
setMatchingDegree(val);
|
||||
const data = await fetchCompetitivenessData(record.userId, jobId);
|
||||
if (data) {
|
||||
setCompetitivenessData(data);
|
||||
}
|
||||
}}
|
||||
>
|
||||
查看匹配详情
|
||||
查看竞争力分析
|
||||
</Button>,
|
||||
],
|
||||
},
|
||||
@@ -182,8 +199,6 @@ function ManagementList() {
|
||||
<Fragment>
|
||||
<div style={{ width: '100%', float: 'right' }}>
|
||||
<ProTable<API.MobileUser.ListRow>
|
||||
// params 是需要自带的参数
|
||||
// 这个参数优先级更高,会覆盖查询表单的参数
|
||||
actionRef={actionRef}
|
||||
formRef={formTableRef}
|
||||
rowKey="jobId"
|
||||
@@ -191,8 +206,6 @@ function ManagementList() {
|
||||
columns={columns}
|
||||
request={(params) =>
|
||||
exportCmsJobCandidates(jobId).then((res) => {
|
||||
// const v = similarityJobs.calculationMatchingDegreeJob(res.rows[0]);
|
||||
// console.log(v);
|
||||
const result = {
|
||||
data: res.rows,
|
||||
total: res.total,
|
||||
@@ -217,14 +230,13 @@ function ManagementList() {
|
||||
]}
|
||||
/>
|
||||
<Detail
|
||||
values={currentRow}
|
||||
open={modalVisible}
|
||||
matching={matchingDegree}
|
||||
loading={competitivenessLoading}
|
||||
competitivenessData={competitivenessData}
|
||||
onClose={() => {
|
||||
console.log('close');
|
||||
setModalVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
setMatchingDegree(null);
|
||||
setCompetitivenessData({});
|
||||
}}
|
||||
></Detail>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,14 @@ import {
|
||||
ProFormText,
|
||||
ProFormTextArea,
|
||||
ProDescriptions,
|
||||
ProFormUploadButton,
|
||||
ProFormRadio,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Form } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect ,useState} from 'react';
|
||||
import { DictValueEnumObj } from '@/components/DictTag';
|
||||
import { getCmsCompanyList } from '@/services/company/list';
|
||||
import { cmsfileupload } from '@/services/Management/list';
|
||||
|
||||
export type ListFormProps = {
|
||||
onCancel: (flag?: boolean, formVals?: unknown) => void;
|
||||
@@ -20,45 +23,133 @@ export type ListFormProps = {
|
||||
educationEnum: DictValueEnumObj;
|
||||
experienceEnum: DictValueEnumObj;
|
||||
areaEnum: DictValueEnumObj;
|
||||
mode?: 'view' | 'edit' | 'create';
|
||||
};
|
||||
|
||||
const waitTime = (time: number = 100) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, time);
|
||||
});
|
||||
isExplainOptions: any;
|
||||
mode?: 'view' | 'edit' | 'create';
|
||||
};
|
||||
|
||||
const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
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;
|
||||
// 预加载公司数据
|
||||
useEffect(() => {
|
||||
if(props.open){
|
||||
form.resetFields();
|
||||
if (props.values) {
|
||||
form.setFieldsValue({
|
||||
...props.values,
|
||||
jobLocationAreaCode: String(props.values.jobLocationAreaCode || ''),
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [form, props.values?.jobId,props.open]);
|
||||
const preloadCompanyData = async () => {
|
||||
if (props.open && props.values?.companyId && props.values?.companyName) {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 1. 先搜索当前公司名称
|
||||
const resData = await getCmsCompanyList({
|
||||
name: props.values.companyName,
|
||||
});
|
||||
|
||||
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 = () => {
|
||||
props.onCancel();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
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) => {
|
||||
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') {
|
||||
return (
|
||||
<ModalForm
|
||||
@@ -68,39 +159,47 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
modalProps={{
|
||||
destroyOnClose: true,
|
||||
onCancel: () => handleCancel(),
|
||||
footer: null,
|
||||
footer: null,
|
||||
}}
|
||||
submitter={false}
|
||||
>
|
||||
<ProDescriptions<API.ManagementList.Manage>
|
||||
column={2}
|
||||
dataSource={props.values || {}}
|
||||
>
|
||||
<ProDescriptions<API.ManagementList.Manage> column={2} dataSource={props.values || {}}>
|
||||
<ProDescriptions.Item dataIndex="jobTitle" label="岗位名称" />
|
||||
<ProDescriptions.Item dataIndex="companyName" label="招聘公司" />
|
||||
<ProDescriptions.Item dataIndex="minSalary" label="最低薪资(元/月)" />
|
||||
<ProDescriptions.Item dataIndex="maxSalary" label="最高薪资(元/月)" />
|
||||
<ProDescriptions.Item
|
||||
dataIndex="education"
|
||||
label="学历要求"
|
||||
valueEnum={educationEnum}
|
||||
/>
|
||||
<ProDescriptions.Item
|
||||
dataIndex="experience"
|
||||
<ProDescriptions.Item dataIndex="education" label="学历要求" valueEnum={educationEnum} />
|
||||
<ProDescriptions.Item
|
||||
dataIndex="experience"
|
||||
label="工作经验"
|
||||
valueEnum={experienceEnum}
|
||||
/>
|
||||
<ProDescriptions.Item
|
||||
dataIndex="jobLocationAreaCode"
|
||||
<ProDescriptions.Item
|
||||
dataIndex="jobLocationAreaCode"
|
||||
label="工作区县"
|
||||
valueEnum={areaEnum}
|
||||
/>
|
||||
<ProDescriptions.Item dataIndex="vacancies" label="招聘人数" />
|
||||
<ProDescriptions.Item dataIndex="jobLocation" label="工作地点" />
|
||||
<ProDescriptions.Item
|
||||
dataIndex="description"
|
||||
label="岗位描述"
|
||||
span={2} // 跨两列显示
|
||||
<ProDescriptions.Item dataIndex="explainUrl" label="视频" />
|
||||
<ProDescriptions.Item dataIndex="cover" label="视频帧封面图" />
|
||||
<ProDescriptions.Item
|
||||
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>
|
||||
</ModalForm>
|
||||
@@ -108,7 +207,6 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
}
|
||||
return (
|
||||
<ModalForm<API.ManagementList.Manage>
|
||||
|
||||
title={mode === 'edit' ? '编辑岗位' : '新建岗位'}
|
||||
form={form}
|
||||
autoFocusFirstInput
|
||||
@@ -119,8 +217,13 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
}}
|
||||
submitTimeout={2000}
|
||||
onFinish={handleFinish}
|
||||
initialValues={{
|
||||
isExplain: 0,
|
||||
}}
|
||||
>
|
||||
<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="companyName" />
|
||||
<ProFormText width="md" name="jobTitle" label="岗位名称" placeholder="请输入岗位名称" />
|
||||
@@ -128,12 +231,39 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
showSearch
|
||||
width="md"
|
||||
name="companyId"
|
||||
onChange={handleChange}
|
||||
request={async ({ keyWords }) => {
|
||||
let resData = await getCmsCompanyList({ name: keyWords });
|
||||
return resData.rows.map((item) => ({ label: item.name, value: item.companyId }));
|
||||
fieldProps={{
|
||||
onChange: (value, option) => {
|
||||
if (option && option.label) {
|
||||
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: '请输入公司名称选择公司!' }]}
|
||||
label="招聘会公司"
|
||||
/>
|
||||
@@ -200,6 +330,26 @@ const listEdit: React.FC<ListFormProps> = (props) => {
|
||||
placeholder="请输入岗位描述"
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -67,6 +67,17 @@ function ManagementList() {
|
||||
const [currentRow, setCurrentRow] = useState<API.ManagementList.Manage>();
|
||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||
const [mode, setMode] = useState<'view' | 'edit' | 'create'>('create');
|
||||
const isExplainOptions = [
|
||||
{
|
||||
label: '关闭显示',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '显示视频',
|
||||
value: 1,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
getDictValueEnum('education', true, true).then((data) => {
|
||||
setEducationEnum(data);
|
||||
@@ -110,12 +121,14 @@ function ManagementList() {
|
||||
dataIndex: 'minSalary',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
render: (text, record) => (record.minSalary ? record.minSalary : '面议'),
|
||||
},
|
||||
{
|
||||
title: '最大薪资(元/月)',
|
||||
dataIndex: 'maxSalary',
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
render: (text, record) => (record.maxSalary ? record.maxSalary : '面议'),
|
||||
},
|
||||
{
|
||||
title: '单位名称',
|
||||
@@ -169,6 +182,7 @@ function ManagementList() {
|
||||
valueType: 'text',
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
render: (text, record) => (record.vacancies== -1 ? '若干' : record.vacancies),
|
||||
},
|
||||
{
|
||||
title: '浏览量',
|
||||
@@ -177,6 +191,15 @@ function ManagementList() {
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: '讲解视频',
|
||||
dataIndex: 'isExplain',
|
||||
valueType: 'text',
|
||||
valueEnum: isPublishEnum,
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
render: (text, record) => (record.cover ? '有' : '无'),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
hideInSearch: true,
|
||||
@@ -220,6 +243,7 @@ function ManagementList() {
|
||||
onClick={() => {
|
||||
setModalVisible(true);
|
||||
setCurrentRow(record);
|
||||
setMode('edit');
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
@@ -254,6 +278,7 @@ function ManagementList() {
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{ width: '100%', float: 'right' }}>
|
||||
@@ -332,6 +357,7 @@ function ManagementList() {
|
||||
setModalVisible(false);
|
||||
setCurrentRow(undefined);
|
||||
}}
|
||||
isExplainOptions={isExplainOptions}
|
||||
educationEnum={educationEnum}
|
||||
experienceEnum={experienceEnum}
|
||||
areaEnum={areaEnum}
|
||||
|
||||
@@ -16,10 +16,9 @@ import { DictValueEnumObj } from '@/components/DictTag';
|
||||
*
|
||||
* @author whiteshader@163.com
|
||||
* @datetime 2023/02/06
|
||||
*
|
||||
*
|
||||
* */
|
||||
|
||||
|
||||
export type UserFormData = Record<string, unknown> & Partial<API.System.User>;
|
||||
|
||||
export type UserFormProps = {
|
||||
@@ -39,7 +38,7 @@ export type UserFormProps = {
|
||||
const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
const [form] = Form.useForm();
|
||||
const userId = Form.useWatch('userId', form);
|
||||
const { sexOptions, statusOptions, } = props;
|
||||
const { sexOptions, statusOptions } = props;
|
||||
const { roles, posts, depts } = props;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,6 +70,9 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
props.onCancel();
|
||||
};
|
||||
const handleFinish = async (values: Record<string, any>) => {
|
||||
if (props.values.userId) {
|
||||
values.userId = props.values.userId;
|
||||
}
|
||||
props.onSubmit(values as UserFormData);
|
||||
};
|
||||
|
||||
@@ -91,7 +93,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
submitter={false}
|
||||
onFinish={handleFinish}>
|
||||
onFinish={handleFinish}
|
||||
>
|
||||
<ProFormText
|
||||
name="nickName"
|
||||
label={intl.formatMessage({
|
||||
@@ -103,9 +106,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
|
||||
),
|
||||
message: <FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -123,9 +124,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: (
|
||||
<FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />
|
||||
),
|
||||
message: <FormattedMessage id="请输入用户部门!" defaultMessage="请输入用户部门!" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -140,9 +139,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
message: (
|
||||
<FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
|
||||
),
|
||||
message: <FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -157,9 +154,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
message: (
|
||||
<FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />
|
||||
),
|
||||
message: <FormattedMessage id="请输入用户邮箱!" defaultMessage="请输入用户邮箱!" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -207,9 +202,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
message: (
|
||||
<FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />
|
||||
),
|
||||
message: <FormattedMessage id="请输入用户性别!" defaultMessage="请输入用户性别!" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -226,9 +219,7 @@ const UserForm: React.FC<UserFormProps> = (props) => {
|
||||
rules={[
|
||||
{
|
||||
required: false,
|
||||
message: (
|
||||
<FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />
|
||||
),
|
||||
message: <FormattedMessage id="请输入帐号状态!" defaultMessage="请输入帐号状态!" />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,32 @@
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { useIntl, FormattedMessage, useAccess } from '@umijs/max';
|
||||
import { Card, Col, Dropdown, FormInstance, Row, Space, Switch } from 'antd';
|
||||
import { Button, message, Modal } from 'antd';
|
||||
import { ActionType, 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 {
|
||||
ActionType,
|
||||
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 { getDictValueEnum } from '@/services/system/dict';
|
||||
import { DataNode } from 'antd/es/tree';
|
||||
@@ -33,10 +54,17 @@ const { confirm } = Modal;
|
||||
const handleAdd = async (fields: API.System.User) => {
|
||||
const hide = message.loading('正在添加');
|
||||
try {
|
||||
await addUser({ ...fields });
|
||||
hide();
|
||||
message.success('添加成功');
|
||||
return true;
|
||||
const rested = await addUser({ ...fields });
|
||||
console.log(rested);
|
||||
if (rested.code === 200) {
|
||||
hide();
|
||||
message.success('添加成功');
|
||||
return true;
|
||||
} else {
|
||||
hide();
|
||||
message.error('添加失败请重试!');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
hide();
|
||||
message.error('添加失败请重试!');
|
||||
@@ -52,10 +80,17 @@ const handleAdd = async (fields: API.System.User) => {
|
||||
const handleUpdate = async (fields: API.System.User) => {
|
||||
const hide = message.loading('正在配置');
|
||||
try {
|
||||
await updateUser(fields);
|
||||
hide();
|
||||
message.success('配置成功');
|
||||
return true;
|
||||
const rested = await updateUser(fields);
|
||||
console.log(rested);
|
||||
if (rested.code === 200) {
|
||||
hide();
|
||||
message.success('配置成功');
|
||||
return true;
|
||||
} else {
|
||||
hide();
|
||||
message.error('配置失败请重试!');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
hide();
|
||||
message.error('配置失败请重试!');
|
||||
@@ -156,12 +191,12 @@ const UserTableList: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
const showChangeStatusConfirm = (record: API.System.User) => {
|
||||
let text = record.status === "1" ? "启用" : "停用";
|
||||
let text = record.status === '1' ? '启用' : '停用';
|
||||
const newStatus = record.status === '0' ? '1' : '0';
|
||||
confirm({
|
||||
title: `确认要${text}${record.userName}用户吗?`,
|
||||
onOk() {
|
||||
changeUserStatus(record.userId, newStatus).then(resp => {
|
||||
changeUserStatus(record.userId, newStatus).then((resp) => {
|
||||
if (resp.code === 200) {
|
||||
messageApi.open({
|
||||
type: 'success',
|
||||
@@ -221,7 +256,7 @@ const UserTableList: React.FC = () => {
|
||||
title: <FormattedMessage id="system.user.dept_name" defaultMessage="部门" />,
|
||||
dataIndex: ['dept', 'deptName'],
|
||||
valueType: 'text',
|
||||
hideInSearch: true
|
||||
hideInSearch: true,
|
||||
},
|
||||
{
|
||||
title: <FormattedMessage id="system.user.phonenumber" defaultMessage="手机号码" />,
|
||||
@@ -241,7 +276,8 @@ const UserTableList: React.FC = () => {
|
||||
unCheckedChildren="停用"
|
||||
defaultChecked
|
||||
onClick={() => showChangeStatusConfirm(record)}
|
||||
/>)
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -297,7 +333,9 @@ const UserTableList: React.FC = () => {
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
label: <FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />,
|
||||
label: (
|
||||
<FormattedMessage id="system.user.reset.password" defaultMessage="密码重置" />
|
||||
),
|
||||
key: 'reset',
|
||||
disabled: !access.hasPerms('system:user:edit.tsx'),
|
||||
},
|
||||
@@ -311,13 +349,12 @@ const UserTableList: React.FC = () => {
|
||||
if (key === 'reset') {
|
||||
setResetPwdModalVisible(true);
|
||||
setCurrentRow(record);
|
||||
}
|
||||
else if (key === 'authRole') {
|
||||
} else if (key === 'authRole') {
|
||||
fetchUserInfo(record.userId);
|
||||
setAuthRoleModalVisible(true);
|
||||
setCurrentRow(record);
|
||||
}
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
<a onClick={(e) => e.preventDefault()}>
|
||||
@@ -369,7 +406,7 @@ const UserTableList: React.FC = () => {
|
||||
const treeData = await getDeptTree({});
|
||||
setDeptTree(treeData);
|
||||
|
||||
const postResp = await getPostList()
|
||||
const postResp = await getPostList();
|
||||
if (postResp.code === 200) {
|
||||
setPostList(
|
||||
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) {
|
||||
setRoleList(
|
||||
roleResp.rows.map((item: any) => {
|
||||
@@ -396,7 +433,8 @@ const UserTableList: React.FC = () => {
|
||||
setModalVisible(true);
|
||||
}}
|
||||
>
|
||||
<PlusOutlined /> <FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
|
||||
<PlusOutlined />{' '}
|
||||
<FormattedMessage id="pages.searchTable.new" defaultMessage="新建" />
|
||||
</Button>,
|
||||
<Button
|
||||
type="primary"
|
||||
@@ -415,7 +453,7 @@ const UserTableList: React.FC = () => {
|
||||
actionRef.current?.reloadAndRest?.();
|
||||
}
|
||||
},
|
||||
onCancel() { },
|
||||
onCancel() {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -435,14 +473,17 @@ const UserTableList: React.FC = () => {
|
||||
</Button>,
|
||||
]}
|
||||
request={(params) =>
|
||||
getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then((res) => {
|
||||
const result = {
|
||||
data: res.rows,
|
||||
total: res.total,
|
||||
success: true,
|
||||
};
|
||||
return result;
|
||||
})
|
||||
getUserList({ ...params, deptId: selectDept.id } as API.System.UserListParams).then(
|
||||
(res) => {
|
||||
console.log(res);
|
||||
const result = {
|
||||
data: res.rows,
|
||||
total: res.total,
|
||||
success: true,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
)
|
||||
}
|
||||
columns={columns}
|
||||
rowSelection={{
|
||||
@@ -490,6 +531,7 @@ const UserTableList: React.FC = () => {
|
||||
<UpdateForm
|
||||
onSubmit={async (values) => {
|
||||
let success = false;
|
||||
console.log({ ...values });
|
||||
if (values.userId) {
|
||||
success = await handleUpdate({ ...values } as API.System.User);
|
||||
} else {
|
||||
|
||||
@@ -57,3 +57,17 @@ export async function getJobTrend(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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,12 +101,15 @@ export async function getRouters(): Promise<any> {
|
||||
}
|
||||
|
||||
export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
|
||||
// childrens = childrens.filter((item) => item.meta !== undefined);
|
||||
return childrens.map((item: API.RoutersMenuItem) => {
|
||||
if (!item.meta && item.children?.length) {
|
||||
item = item.children[0]
|
||||
}
|
||||
return {
|
||||
path: item.path,
|
||||
icon: createIcon(item.meta.icon),
|
||||
// icon: item.meta.icon,
|
||||
name: item.meta.title,
|
||||
icon: item.meta && createIcon(item.meta.icon),
|
||||
name: item.meta && item.meta.title,
|
||||
routes: item.children ? convertCompatRouters(item.children) : undefined,
|
||||
hideChildrenInMenu: item.hidden,
|
||||
hideInMenu: item.hidden,
|
||||
@@ -119,6 +122,8 @@ export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] {
|
||||
export async function getRoutersInfo(): Promise<MenuDataItem[]> {
|
||||
return getRouters().then((res) => {
|
||||
if (res.code === 200) {
|
||||
console.log(res.data)
|
||||
console.log(convertCompatRouters(res.data))
|
||||
return convertCompatRouters(res.data);
|
||||
} else {
|
||||
return [];
|
||||
|
||||
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;
|
||||
isApply?: number;
|
||||
isCollection?: number;
|
||||
isExplain?: number;
|
||||
isHot?: number;
|
||||
jobId?: number;
|
||||
jobLocation?: string;
|
||||
@@ -31,6 +32,7 @@ declare namespace API.ManagementList {
|
||||
experience?: string;
|
||||
isApply?: number;
|
||||
isCollection?: number;
|
||||
isExplain?: number;
|
||||
isHot?: number;
|
||||
jobId?: number;
|
||||
jobLocation?: string;
|
||||
@@ -70,3 +72,29 @@ declare namespace API.ManagementList {
|
||||
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