Compare commits

...

10 Commits

Author SHA1 Message Date
Apcallover
2a930bf682 flat: 123
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled
2025-12-19 18:19:30 +08:00
Apcallover
e7ad0c1567 flat:石河子 2025-12-19 17:56:54 +08:00
bin
5b6393a37d Merge remote-tracking branch 'origin/main' into refactor-matchRadar 2025-12-09 18:42:01 +08:00
bin
e034f117e8 fix 岗位列表.,企业列表/弹窗回显问题 2025-12-09 18:41:42 +08:00
bin
49f48b132d delete copyFile 2025-12-09 14:45:00 +08:00
bin
388dc6c9fa refactor : 岗位列表-申请人-查看竞争力分析 2025-12-09 14:44:09 +08:00
Apcallover
0fef7b57a8 flat: 暂存 2025-12-07 13:56:45 +08:00
Apcallover
16f456d2f2 flat:暂存 2025-12-07 12:42:02 +08:00
Apcallover
6007a69e8a flat:菜单优化 2025-12-04 21:11:52 +08:00
Apcallover
4b0c7033e2 flat:暂存 2025-12-03 10:21:51 +08:00
17 changed files with 751 additions and 333 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ _roadhog-api-doc
# production
/dist
/shihezi
/qingdao
# misc

138
README.md
View File

@@ -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.
[![Build Status](https://dev.azure.com/ant-design/ant-design-pro/_apis/build/status/ant-design.ant-design-pro?branchName=master)](https://dev.azure.com/ant-design/ant-design-pro/_build/latest?definitionId=1?branchName=master) ![Github Action](https://github.com/ant-design/ant-design-pro/workflows/Node%20CI/badge.svg) ![Deploy](https://github.com/ant-design/ant-design-pro/workflows/Deploy%20CI/badge.svg)
[![Gitter](https://img.shields.io/gitter/room/ant-design/pro-english.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjEyMzUiIGhlaWdodD0iNjUwIiB2aWV3Qm94PSIwIDAgNzQxMCAzOTAwIj4NCjxyZWN0IHdpZHRoPSI3NDEwIiBoZWlnaHQ9IjM5MDAiIGZpbGw9IiNiMjIyMzQiLz4NCjxwYXRoIGQ9Ik0wLDQ1MEg3NDEwbTAsNjAwSDBtMCw2MDBINzQxMG0wLDYwMEgwbTAsNjAwSDc0MTBtMCw2MDBIMCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjMwMCIvPg0KPHJlY3Qgd2lkdGg9IjI5NjQiIGhlaWdodD0iMjEwMCIgZmlsbD0iIzNjM2I2ZSIvPg0KPGcgZmlsbD0iI2ZmZiI%2BDQo8ZyBpZD0iczE4Ij4NCjxnIGlkPSJzOSI%2BDQo8ZyBpZD0iczUiPg0KPGcgaWQ9InM0Ij4NCjxwYXRoIGlkPSJzIiBkPSJNMjQ3LDkwIDMxNy41MzQyMzAsMzA3LjA4MjAzOSAxMzIuODczMjE4LDE3Mi45MTc5NjFIMzYxLjEyNjc4MkwxNzYuNDY1NzcwLDMwNy4wODIwMzl6Ii8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB5PSI0MjAiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHk9Ijg0MCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTI2MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjcyIgeT0iMTY4MCIvPg0KPC9nPg0KPHVzZSB4bGluazpocmVmPSIjczQiIHg9IjI0NyIgeT0iMjEwIi8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzOSIgeD0iNDk0Ii8%2BDQo8L2c%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzMTgiIHg9Ijk4OCIvPg0KPHVzZSB4bGluazpocmVmPSIjczkiIHg9IjE5NzYiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3M1IiB4PSIyNDcwIi8%2BDQo8L2c%2BDQo8L3N2Zz4%3D)](https://gitter.im/ant-design/pro-english?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/ant-design/ant-design-pro](https://img.shields.io/gitter/room/ant-design/ant-design-pro.svg?style=flat-square&logoWidth=20&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4NCjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjkwMCIgaGVpZ2h0PSI2MDAiIHZpZXdCb3g9IjAgMCAzMCAyMCI%2BDQo8ZGVmcz4NCjxwYXRoIGlkPSJzIiBkPSJNMCwtMSAwLjU4Nzc4NSwwLjgwOTAxNyAtMC45NTEwNTcsLTAuMzA5MDE3SDAuOTUxMDU3TC0wLjU4Nzc4NSwwLjgwOTAxN3oiIGZpbGw9IiNmZmRlMDAiLz4NCjwvZGVmcz4NCjxyZWN0IHdpZHRoPSIzMCIgaGVpZ2h0PSIyMCIgZmlsbD0iI2RlMjkxMCIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNSw1KSBzY2FsZSgzKSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsMikgcm90YXRlKDIzLjAzNjI0MykiLz4NCjx1c2UgeGxpbms6aHJlZj0iI3MiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEyLDQpIHJvdGF0ZSg0NS44Njk4OTgpIi8%2BDQo8dXNlIHhsaW5rOmhyZWY9IiNzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgxMiw3KSByb3RhdGUoNjkuOTQ1Mzk2KSIvPg0KPHVzZSB4bGluazpocmVmPSIjcyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTAsOSkgcm90YXRlKDIwLjY1OTgwOCkiLz4NCjwvc3ZnPg%3D%3D)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build With Umi](https://img.shields.io/badge/build%20with-umi-028fe4.svg?style=flat-square)](http://umijs.org/) ![](https://badgen.net/badge/icon/Ant%20Design?icon=https://gw.alipayobjects.com/zos/antfincdn/Pp4WPgVDB3/KDpgvguMpGfqaHPjicRK.svg&label)
![](https://user-images.githubusercontent.com/8186664/44953195-581e3d80-aec4-11e8-8dcb-54b9db38ec11.png)
</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

View File

@@ -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

Binary file not shown.

View File

@@ -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'

View File

@@ -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,
},
]}

View File

@@ -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,
}),
});
}

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>
);
};

View File

@@ -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}

View File

@@ -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="请输入帐号状态!" />,
},
]}
/>

View File

@@ -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 {

View File

@@ -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,
});
}

View File

@@ -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 [];

View File

@@ -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;
}
}