正在显示
79 个修改的文件
包含
3040 行增加
和
1 行删除
.eslintrc.json
0 → 100644
1 | +{ | ||
2 | + "env": { | ||
3 | + "browser": true, | ||
4 | + "es2021": true | ||
5 | + }, | ||
6 | + "extends": [ | ||
7 | + "eslint:recommended", | ||
8 | + "plugin:react/recommended", | ||
9 | + "plugin:@typescript-eslint/recommended" | ||
10 | + ], | ||
11 | + "overrides": [], | ||
12 | + "parser": "@typescript-eslint/parser", | ||
13 | + "parserOptions": { | ||
14 | + "ecmaVersion": "latest" | ||
15 | + }, | ||
16 | + "plugins": ["react", "@typescript-eslint"], | ||
17 | + "rules": { | ||
18 | + "react/react-in-jsx-scope": "off", // 无需在组件内引用React | ||
19 | + "@typescript-eslint/explicit-module-boundary-types": "off", //关闭函数返回类型 | ||
20 | + "arrow-spacing": ["error", { "before": true, "after": true }], | ||
21 | + "semi": ["error", "always"], | ||
22 | + "comma-spacing": ["error", { "before": false, "after": true }], | ||
23 | + "complexity": 0, | ||
24 | + "getter-return": ["error", { "allowImplicit": true }], | ||
25 | + "no-unused-vars": ["error", { "vars": "local" }, { "args": "after-used" }], | ||
26 | + "no-debugger": "error", | ||
27 | + "no-empty": ["error", { "allowEmptyCatch": true }], | ||
28 | + "array-callback-return": "error", | ||
29 | + "eqeqeq": ["error", "always"], | ||
30 | + "no-eval": ["error", { "allowIndirect": true }], | ||
31 | + "indent": ["error", 2], | ||
32 | + "linebreak-style": "off", | ||
33 | + "@typescript-eslint/no-empty-function": "off", // 空函数非禁止使用 | ||
34 | + "react/display-name": "off", //防止在React组件定义中丢失displayName | ||
35 | + "camelcase": ["error", { "properties": "always" }], // 强制驼峰格式 | ||
36 | + "require-await": "error", // 禁止使用不带 await 表达式的 async 函数 | ||
37 | + "no-label-var": "error", // 不允许标签与变量同名 | ||
38 | + "array-bracket-spacing": ["error", "never"], // 强制数组方括号中使用一致的空格 | ||
39 | + "key-spacing": ["error", { "afterColon": true }], // 要求在对象字面量的冒号和值之间存在至少有一个空格 | ||
40 | + "lines-around-comment": ["error", { "beforeBlockComment": true }], // 要求在块级注释之前有一空行 | ||
41 | + "prefer-destructuring": ["error", { "object": true, "array": true }] //优先使用数组和对象解构 | ||
42 | + } | ||
43 | +} |
.gitignore
0 → 100644
.lintstagedrc.json
0 → 100644
.prettierrc
0 → 100644
.stylelintrc.js
0 → 100644
1 | +module.exports = { | ||
2 | + extends: [ | ||
3 | + 'stylelint-config-standard', | ||
4 | + 'stylelint-prettier/recommended', | ||
5 | + 'stylelint-config-rational-order', | ||
6 | + ], | ||
7 | + // 使用 stylelint 来 lint css in js? https://github.com/emotion-js/emotion/discussions/2694 | ||
8 | + rules: { | ||
9 | + 'function-name-case': ['lower'], | ||
10 | + 'function-no-unknown': [ | ||
11 | + true, | ||
12 | + { | ||
13 | + ignoreFunctions: [ | ||
14 | + 'fade', | ||
15 | + 'fadeout', | ||
16 | + 'tint', | ||
17 | + 'darken', | ||
18 | + 'ceil', | ||
19 | + 'fadein', | ||
20 | + 'floor', | ||
21 | + 'unit', | ||
22 | + 'shade', | ||
23 | + 'lighten', | ||
24 | + 'percentage', | ||
25 | + '-', | ||
26 | + ], | ||
27 | + }, | ||
28 | + ], | ||
29 | + 'import-notation': null, | ||
30 | + 'no-descending-specificity': null, | ||
31 | + 'no-invalid-position-at-import-rule': null, | ||
32 | + 'declaration-empty-line-before': null, | ||
33 | + 'keyframes-name-pattern': null, | ||
34 | + 'custom-property-pattern': null, | ||
35 | + 'number-max-precision': 8, | ||
36 | + 'alpha-value-notation': 'number', | ||
37 | + 'color-function-notation': 'legacy', | ||
38 | + 'selector-class-pattern': null, | ||
39 | + 'selector-id-pattern': null, | ||
40 | + 'selector-not-notation': null, | ||
41 | + }, | ||
42 | +}; |
index.html
0 → 100644
1 | +<!DOCTYPE html> | ||
2 | +<html lang="en" class="dark"> | ||
3 | + <head> | ||
4 | + <meta charset="UTF-8" /> | ||
5 | + <link rel="icon" href="/favicon.ico" /> | ||
6 | + <link rel="stylesheet" href="/image/partten/iconfont.css" /> | ||
7 | + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
8 | + <title></title> | ||
9 | + </head> | ||
10 | + <body> | ||
11 | + <div id="app"></div> | ||
12 | + <script type="module" src="/index.tsx"></script> | ||
13 | + </body> | ||
14 | +</html> |
index.tsx
0 → 100644
@@ -5,7 +5,7 @@ | @@ -5,7 +5,7 @@ | ||
5 | "main": "index.js", | 5 | "main": "index.js", |
6 | "scripts": { | 6 | "scripts": { |
7 | "dev": "vite", | 7 | "dev": "vite", |
8 | - "build": "vite build", | 8 | + "build": "vite build && tsc --declaration --emitDeclarationOnly --noEmitOnError false", |
9 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", | 9 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", |
10 | "lint-staged": "lint-staged", | 10 | "lint-staged": "lint-staged", |
11 | "test": "echo \"Error: no test specified\" && exit 1" | 11 | "test": "echo \"Error: no test specified\" && exit 1" |
packages/hr/App.tsx
0 → 100644
1 | +import { useMemo } from 'react'; | ||
2 | +import AddTags from '@/hr/add_tags'; | ||
3 | +import { FormProvider } from '@formily/react'; | ||
4 | +import { createForm } from '@formily/core'; | ||
5 | +import SearchWrap from '@/hr/search/search'; | ||
6 | +import InputField from '@/hr/antd_input'; | ||
7 | + | ||
8 | +const App = () => { | ||
9 | + const form = useMemo(() => createForm(), []); | ||
10 | + return ( | ||
11 | + <FormProvider form={form}> | ||
12 | + <SearchWrap leftSectionComponents={[<InputField.text name="123" />]} /> | ||
13 | + </FormProvider> | ||
14 | + ); | ||
15 | +}; | ||
16 | + | ||
17 | +export default App; |
packages/hr/add_tags/index.less
0 → 100644
1 | +.Ok { | ||
2 | + font-size: 14px; | ||
3 | + font-weight: 400; | ||
4 | + color: #1677ff; | ||
5 | + margin-left: 21px; | ||
6 | + cursor: pointer; | ||
7 | +} | ||
8 | + | ||
9 | +.cancle { | ||
10 | + font-size: 14px; | ||
11 | + font-weight: 400; | ||
12 | + color: #606266; | ||
13 | + cursor: pointer; | ||
14 | + margin-left: 10px; | ||
15 | +} | ||
16 | + | ||
17 | +.render-tag { | ||
18 | + background-color: #f2f5fc; | ||
19 | + border-radius: 4px; | ||
20 | + padding: 5px 18px; | ||
21 | + font-size: 14px; | ||
22 | + font-weight: 300; | ||
23 | + // color: #333333; | ||
24 | + border-style: solid !important; | ||
25 | + line-height: 22px; | ||
26 | + border-color: #f2f5fc; | ||
27 | + | ||
28 | + &-checkable:not(&-checkable-checked):hover { | ||
29 | + background-color: rgba(22, 119, 255, 0.5); | ||
30 | + color: #fff; | ||
31 | + } | ||
32 | + | ||
33 | + &-checkable-checked { | ||
34 | + background-color: #1677ff; | ||
35 | + color: #fff; | ||
36 | + font-weight: 400; | ||
37 | + | ||
38 | + &:hover { | ||
39 | + background-color: #4096ff !important; | ||
40 | + } | ||
41 | + } | ||
42 | +} | ||
43 | + | ||
44 | +.render-tags { | ||
45 | + background-color: #f2f5fc; | ||
46 | + border-radius: 4px; | ||
47 | + padding: 5px 18px; | ||
48 | + font-size: 14px; | ||
49 | + font-weight: 300; | ||
50 | + border-style: solid !important; | ||
51 | + line-height: 22px; | ||
52 | + border-color: #f2f5fc; | ||
53 | + | ||
54 | + &-checkable:not(&-checkable-checked):hover { | ||
55 | + background-color: rgba(22, 119, 255, 0.5); | ||
56 | + color: #fff; | ||
57 | + } | ||
58 | + | ||
59 | + &-checkable-checked { | ||
60 | + background-color: #1677ff; | ||
61 | + color: #fff; | ||
62 | + font-weight: 400; | ||
63 | + | ||
64 | + &:hover { | ||
65 | + background-color: #4096ff !important; | ||
66 | + } | ||
67 | + } | ||
68 | +} | ||
69 | + | ||
70 | +.ovner-input { | ||
71 | + width: 220px; | ||
72 | + height: 34px; | ||
73 | + line-height: 34px; | ||
74 | + vertical-align: top; | ||
75 | + margin-right: 5px; | ||
76 | +} |
packages/hr/add_tags/index.tsx
0 → 100644
1 | +import React, { useEffect, useRef, useState } from 'react'; | ||
2 | +import { PlusOutlined } from '@ant-design/icons'; | ||
3 | +import { InputRef } from 'antd'; | ||
4 | +import { Space, Input, Tag, Tooltip } from 'antd'; | ||
5 | +import './index.less'; | ||
6 | +import { Field } from '@formily/react'; | ||
7 | +import { FieldProps } from '../typings'; | ||
8 | +import { FormItem } from '@formily/antd-v5'; | ||
9 | + | ||
10 | +interface Props { | ||
11 | + initValues?: string[]; | ||
12 | + tag_text?: string; | ||
13 | + showValues?: boolean; | ||
14 | + onChange?: (value: string[]) => void; | ||
15 | + value?: string[]; | ||
16 | + maxLength?: number; | ||
17 | +} | ||
18 | + | ||
19 | +export const AddTag = (props: Props) => { | ||
20 | + const { onChange, tag_text, showValues = true, value = [], maxLength = 0 } = props; | ||
21 | + | ||
22 | + const [inputVisible, setInputVisible] = useState(false); | ||
23 | + const [inputValue, setInputValue] = useState(''); | ||
24 | + const inputRef = useRef<InputRef>(null); | ||
25 | + | ||
26 | + useEffect(() => { | ||
27 | + if (inputVisible) { | ||
28 | + inputRef.current?.focus(); | ||
29 | + } | ||
30 | + }, [inputVisible]); | ||
31 | + | ||
32 | + const handleClose = (removedTag: string) => { | ||
33 | + const newTags = value?.filter((tag) => tag !== removedTag) || []; | ||
34 | + | ||
35 | + onChange && onChange([...newTags]); | ||
36 | + }; | ||
37 | + | ||
38 | + const showInput = () => { | ||
39 | + setInputVisible(true); | ||
40 | + }; | ||
41 | + | ||
42 | + const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
43 | + setInputValue(e.target.value); | ||
44 | + }; | ||
45 | + | ||
46 | + const handleInputConfirm = () => { | ||
47 | + if (inputValue && value?.indexOf(inputValue) === -1) { | ||
48 | + onChange && onChange([...value, inputValue]); | ||
49 | + } | ||
50 | + setInputVisible(false); | ||
51 | + setInputValue(''); | ||
52 | + }; | ||
53 | + | ||
54 | + return ( | ||
55 | + <Space size={10} wrap> | ||
56 | + {showValues && ( | ||
57 | + <Space size={10} wrap> | ||
58 | + {value && | ||
59 | + value.map((tag, index) => { | ||
60 | + const isLongTag = tag.length > 20; | ||
61 | + const tagElem = ( | ||
62 | + <Tag | ||
63 | + key={index} | ||
64 | + closable | ||
65 | + style={{ | ||
66 | + userSelect: 'none', | ||
67 | + }} | ||
68 | + className="render-tag" | ||
69 | + onClose={() => handleClose(tag)} | ||
70 | + > | ||
71 | + <span>{isLongTag ? `${tag.slice(0, 20)}...` : tag}</span> | ||
72 | + </Tag> | ||
73 | + ); | ||
74 | + return isLongTag ? ( | ||
75 | + <Tooltip title={tag} key={tag}> | ||
76 | + {tagElem} | ||
77 | + </Tooltip> | ||
78 | + ) : ( | ||
79 | + tagElem | ||
80 | + ); | ||
81 | + })} | ||
82 | + </Space> | ||
83 | + )} | ||
84 | + {inputVisible ? ( | ||
85 | + <> | ||
86 | + <Input | ||
87 | + ref={inputRef} | ||
88 | + type="text" | ||
89 | + size="small" | ||
90 | + className="ovner-input" | ||
91 | + value={inputValue} | ||
92 | + onChange={handleInputChange} | ||
93 | + onPressEnter={handleInputConfirm} | ||
94 | + /> | ||
95 | + <span onClick={handleInputConfirm} className="Ok"> | ||
96 | + 确定 | ||
97 | + </span> | ||
98 | + <span | ||
99 | + onClick={() => { | ||
100 | + setInputVisible(false); | ||
101 | + }} | ||
102 | + className="cancle" | ||
103 | + > | ||
104 | + 取消 | ||
105 | + </span> | ||
106 | + </> | ||
107 | + ) : ( | ||
108 | + (!maxLength || !(maxLength <= value.length)) && ( | ||
109 | + <Tag onClick={showInput} className="render-tags" color="#1677FF"> | ||
110 | + <PlusOutlined /> {tag_text || '添加标签'} | ||
111 | + </Tag> | ||
112 | + ) | ||
113 | + )} | ||
114 | + </Space> | ||
115 | + ); | ||
116 | +}; | ||
117 | + | ||
118 | +interface AddTagsProps extends FieldProps { | ||
119 | + name: string; | ||
120 | + title?: string; | ||
121 | + initValues?: string[]; | ||
122 | +} | ||
123 | +const AddTags: React.FC<AddTagsProps> = (props) => { | ||
124 | + const { name, title, decoratorProps, componentProps, validator } = props; | ||
125 | + | ||
126 | + return ( | ||
127 | + <Field | ||
128 | + name={name} | ||
129 | + title={title} | ||
130 | + decorator={[FormItem, { ...decoratorProps }]} | ||
131 | + component={[ | ||
132 | + AddTag, | ||
133 | + { | ||
134 | + ...componentProps, | ||
135 | + }, | ||
136 | + ]} | ||
137 | + validator={validator} | ||
138 | + /> | ||
139 | + ); | ||
140 | +}; | ||
141 | + | ||
142 | +export default AddTags; |
1 | +#root .global_cascader { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-select { | ||
12 | + &-selector { | ||
13 | + height: 36px; | ||
14 | + border-color: #ebeef5; | ||
15 | + border-radius: 2px; | ||
16 | + padding-left: 13px; | ||
17 | + } | ||
18 | + | ||
19 | + &-arrow { | ||
20 | + color: #adaeb1; | ||
21 | + } | ||
22 | + | ||
23 | + &-selection-placeholder { | ||
24 | + color: #ccc; | ||
25 | + font-weight: 400; | ||
26 | + line-height: 36px; | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | + } | ||
32 | + } | ||
33 | +} |
1 | +import { Field } from '@formily/react'; | ||
2 | +import { FormItem, Cascader } from '@formily/antd-v5'; | ||
3 | +import cx from 'classnames'; | ||
4 | +import './index.less'; | ||
5 | +import { FieldProps } from '../../typings'; | ||
6 | + | ||
7 | +/** | ||
8 | + * An interface that defines an option in the Cascader component. | ||
9 | + */ | ||
10 | +interface Option { | ||
11 | + value: string | number; | ||
12 | + label: string; | ||
13 | + disabled?: boolean; | ||
14 | + children?: Option[]; | ||
15 | +} | ||
16 | + | ||
17 | +/** | ||
18 | + * An interface that defines the properties of the CascaderInput component. | ||
19 | + */ | ||
20 | +export interface CascaderInputProps extends FieldProps { | ||
21 | + options: Option[]; | ||
22 | +} | ||
23 | + | ||
24 | +/** | ||
25 | + * The CascaderInput component. | ||
26 | + * | ||
27 | + * This is a wrapper around the Cascader component that makes it easier to use in a Formily form. | ||
28 | + */ | ||
29 | +const CascaderInput: React.FC<CascaderInputProps> = (props) => { | ||
30 | + const { name, title, validator = [], decoratorProps, componentProps, options } = props; | ||
31 | + | ||
32 | + return ( | ||
33 | + <div className={cx('global_cascader')}> | ||
34 | + <Field | ||
35 | + {...props} | ||
36 | + name={name} | ||
37 | + title={title} | ||
38 | + dataSource={options} | ||
39 | + decorator={[FormItem, { ...decoratorProps }]} | ||
40 | + component={[ | ||
41 | + Cascader, | ||
42 | + { | ||
43 | + allowClear: true, | ||
44 | + ...componentProps, | ||
45 | + componenttypename: 'Cascader', | ||
46 | + }, | ||
47 | + ]} | ||
48 | + validator={validator} | ||
49 | + /> | ||
50 | + </div> | ||
51 | + ); | ||
52 | +}; | ||
53 | + | ||
54 | +export default CascaderInput; |
1 | +#root .global_checkbox { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-checkbox-group { | ||
12 | + .ant-checkbox-wrapper { | ||
13 | + .ant-checkbox { | ||
14 | + &-inner { | ||
15 | + border-color: #ebeef5; | ||
16 | + border-radius: 2px; | ||
17 | + } | ||
18 | + } | ||
19 | + } | ||
20 | + } | ||
21 | + } | ||
22 | + } | ||
23 | + } | ||
24 | + } | ||
25 | +} |
1 | + | ||
2 | +import { FormItem, Checkbox } from '@formily/antd-v5'; | ||
3 | +import { Field } from '@formily/react'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | + | ||
8 | +interface Option { | ||
9 | + value: string | number; | ||
10 | + label: string; | ||
11 | + disabled?: boolean; | ||
12 | +} | ||
13 | + | ||
14 | +export interface P extends FieldProps { | ||
15 | + options: Option[]; | ||
16 | +} | ||
17 | + | ||
18 | +const CheckboxGroup: React.FC<P> = (props) => { | ||
19 | + const { name, title, options = [], validator = [], decoratorProps, componentProps } = props; | ||
20 | + | ||
21 | + return ( | ||
22 | + <div className={cx('global_checkbox')}> | ||
23 | + <Field | ||
24 | + {...props} | ||
25 | + name={name} | ||
26 | + title={title} | ||
27 | + dataSource={options} | ||
28 | + decorator={[FormItem, { ...decoratorProps }]} | ||
29 | + component={[ | ||
30 | + Checkbox.Group, | ||
31 | + { | ||
32 | + ...componentProps, | ||
33 | + componenttypename: 'Checkbox.Group', | ||
34 | + }, | ||
35 | + ]} | ||
36 | + validator={validator} | ||
37 | + /> | ||
38 | + </div> | ||
39 | + ); | ||
40 | +}; | ||
41 | + | ||
42 | +export default CheckboxGroup; |
packages/hr/antd_input/date_input/index.less
0 → 100644
1 | +#root .global_date { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-picker { | ||
12 | + border-color: #ebeef5; | ||
13 | + border-radius: 2px; | ||
14 | + padding: 6px 13px; | ||
15 | + | ||
16 | + &-input { | ||
17 | + > input::placeholder { | ||
18 | + color: #ccc; | ||
19 | + font-weight: 400; | ||
20 | + } | ||
21 | + } | ||
22 | + | ||
23 | + &-suffix { | ||
24 | + color: #606266; | ||
25 | + } | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | +} |
packages/hr/antd_input/date_input/index.tsx
0 → 100644
1 | + | ||
2 | +import { FormItem, DatePicker } from '@formily/antd-v5'; | ||
3 | +import { Field } from '@formily/react'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | + | ||
8 | +const DateInput: React.FC<FieldProps> = (props) => { | ||
9 | + const { name, title, validator = [], format, decoratorProps, componentProps } = props; | ||
10 | + | ||
11 | + return ( | ||
12 | + <div className={cx('global_date')}> | ||
13 | + <Field | ||
14 | + {...props} | ||
15 | + name={name} | ||
16 | + title={title} | ||
17 | + decorator={[FormItem, { ...decoratorProps }]} | ||
18 | + component={[ | ||
19 | + DatePicker, | ||
20 | + { | ||
21 | + allowClear: true, | ||
22 | + format: format || 'YYYY/MM/DD', | ||
23 | + ...componentProps, | ||
24 | + componenttypename: 'DatePicker', | ||
25 | + }, | ||
26 | + ]} | ||
27 | + validator={validator} | ||
28 | + /> | ||
29 | + </div> | ||
30 | + ); | ||
31 | +}; | ||
32 | + | ||
33 | +export default DateInput; |
packages/hr/antd_input/index.tsx
0 → 100644
1 | +import RadioGroup from './radio_group'; | ||
2 | +import CheckboxGroup from './checkbox_input'; | ||
3 | +import CascaderInput from './cascader_input'; | ||
4 | +import TextAreaInput from './text_area_input'; | ||
5 | +import SelectInput from './select'; | ||
6 | +import NumberInput from './number_input'; | ||
7 | +import TextInput from './text_input'; | ||
8 | +import DateInput from './date_input'; | ||
9 | +import RangeDate from './range_date'; | ||
10 | +import SearchInput from './search_input'; | ||
11 | +import SelectMap from './map_select'; | ||
12 | +import RadioButton from './radio_button'; | ||
13 | +import UploadWrap from './uploader/UploadWrap'; | ||
14 | + | ||
15 | +const InputItem = () => <div></div>; | ||
16 | + | ||
17 | +InputItem.search = SearchInput; | ||
18 | +InputItem.rangeDate = RangeDate; | ||
19 | +InputItem.date = DateInput; | ||
20 | +InputItem.select = SelectInput; | ||
21 | +InputItem.selectMap = SelectMap; | ||
22 | +InputItem.number = NumberInput; | ||
23 | +InputItem.text = TextInput; | ||
24 | +InputItem.textArea = TextAreaInput; | ||
25 | +InputItem.cascader = CascaderInput; | ||
26 | +InputItem.checkbox = CheckboxGroup; | ||
27 | +InputItem.radio = RadioGroup; | ||
28 | +InputItem.radioBtn = RadioButton; | ||
29 | +InputItem.uploader = UploadWrap; | ||
30 | + | ||
31 | +export default InputItem; |
packages/hr/antd_input/map_select/index.less
0 → 100644
1 | +#root .global_map_input { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + // line-height: 0 !important; | ||
12 | + .ant-select-compact-item:not(.ant-select-compact-last-item) { | ||
13 | + margin-right: 10px; | ||
14 | + } | ||
15 | + | ||
16 | + .ant-select { | ||
17 | + &-selector { | ||
18 | + height: 36px; | ||
19 | + border-color: #ebeef5; | ||
20 | + border-radius: 2px; | ||
21 | + padding-left: 13px; | ||
22 | + } | ||
23 | + | ||
24 | + &-arrow { | ||
25 | + color: #adaeb1; | ||
26 | + } | ||
27 | + | ||
28 | + &-selection-placeholder { | ||
29 | + color: #ccc; | ||
30 | + font-weight: 400; | ||
31 | + line-height: 36px; | ||
32 | + } | ||
33 | + } | ||
34 | + } | ||
35 | + } | ||
36 | + } | ||
37 | + } | ||
38 | +} |
packages/hr/antd_input/map_select/index.tsx
0 → 100644
1 | +import { Field } from '@formily/react'; | ||
2 | +import { FormItem } from '@formily/antd-v5'; | ||
3 | +import cx from 'classnames'; | ||
4 | +import './index.less'; | ||
5 | +import { FieldProps } from '../../typings'; | ||
6 | +import MapSelect from '@/hr/map_select'; | ||
7 | + | ||
8 | +const SelectMap: React.FC<FieldProps> = (props) => { | ||
9 | + const { name, addressName, title, validator = [], decoratorProps, componentProps } = props; | ||
10 | + | ||
11 | + return ( | ||
12 | + <div className={cx('global_map_input')}> | ||
13 | + <Field | ||
14 | + {...props} | ||
15 | + name={name} | ||
16 | + title={title} | ||
17 | + decorator={[FormItem, { ...decoratorProps }]} | ||
18 | + component={[ | ||
19 | + MapSelect, | ||
20 | + { | ||
21 | + allowClear: true, | ||
22 | + ...componentProps, | ||
23 | + addressName: addressName, | ||
24 | + componenttypename: 'MapSelect', | ||
25 | + }, | ||
26 | + ]} | ||
27 | + validator={validator} | ||
28 | + /> | ||
29 | + </div> | ||
30 | + ); | ||
31 | +}; | ||
32 | + | ||
33 | +export default SelectMap; |
1 | +#root .global_number { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + line-height: 0 !important; | ||
12 | + | ||
13 | + .ant-input-number { | ||
14 | + border-color: #ebeef5; | ||
15 | + border-radius: 2px; | ||
16 | + | ||
17 | + &-input { | ||
18 | + padding: 6px 13px; | ||
19 | + height: 34px; | ||
20 | + | ||
21 | + &-wrap { | ||
22 | + > input::placeholder { | ||
23 | + color: #ccc; | ||
24 | + font-weight: 400; | ||
25 | + line-height: 34px; | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | + } | ||
32 | + } | ||
33 | + } | ||
34 | +} |
1 | + | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import { FormItem } from '@formily/antd-v5'; | ||
4 | +import { InputNumber } from 'antd'; | ||
5 | +import cx from 'classnames'; | ||
6 | +import './index.less'; | ||
7 | +import { FieldProps } from '../../typings'; | ||
8 | + | ||
9 | +const NumberInput: React.FC<FieldProps> = (props) => { | ||
10 | + const { name, title, validator = [], decoratorProps, componentProps } = props; | ||
11 | + | ||
12 | + return ( | ||
13 | + <div className={cx('global_number')}> | ||
14 | + <Field | ||
15 | + {...props} | ||
16 | + name={name} | ||
17 | + title={title} | ||
18 | + decorator={[ | ||
19 | + FormItem, | ||
20 | + { | ||
21 | + ...decoratorProps, | ||
22 | + }, | ||
23 | + ]} | ||
24 | + component={[ | ||
25 | + InputNumber, | ||
26 | + { | ||
27 | + allowClear: true, | ||
28 | + ...componentProps, | ||
29 | + componenttypename: 'InputNumber', | ||
30 | + }, | ||
31 | + ]} | ||
32 | + validator={validator} | ||
33 | + /> | ||
34 | + </div> | ||
35 | + ); | ||
36 | +}; | ||
37 | + | ||
38 | +export default NumberInput; |
1 | +#root .global_radio_button { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + .ant-radio-group { | ||
9 | + .ant-radio-button-wrapper { | ||
10 | + border-radius: 4px; | ||
11 | + min-width: 140px; | ||
12 | + text-align: center; | ||
13 | + padding: 0; | ||
14 | + font-size: 14px; | ||
15 | + font-weight: 500; | ||
16 | + line-height: 36px; | ||
17 | + height: auto; | ||
18 | + background: #f2f3f5; | ||
19 | + border-color: #f2f3f5; | ||
20 | + | ||
21 | + &:not(:first-child) { | ||
22 | + margin-left: 10px; | ||
23 | + border-inline-start-width: 1px; | ||
24 | + | ||
25 | + &::before { | ||
26 | + width: 0; | ||
27 | + } | ||
28 | + } | ||
29 | + | ||
30 | + &-checked:not(&-disabled) { | ||
31 | + background-color: #4096ff; | ||
32 | + color: #fff; | ||
33 | + border-color: #4096ff; | ||
34 | + } | ||
35 | + | ||
36 | + &-disabled.ant-radio-button-wrapper-checked { | ||
37 | + background: rgba(22, 119, 255, 0.6); | ||
38 | + color: #fff; | ||
39 | + } | ||
40 | + } | ||
41 | + } | ||
42 | + } | ||
43 | +} |
1 | +import { FormItem, Radio } from '@formily/antd-v5'; | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import cx from 'classnames'; | ||
4 | +import './index.less'; | ||
5 | +import { FieldProps } from '../../typings'; | ||
6 | + | ||
7 | +/** | ||
8 | + * An interface that defines an option in the RadioButton component. | ||
9 | + */ | ||
10 | +interface Option { | ||
11 | + value: string | number; | ||
12 | + label: string; | ||
13 | + disabled?: boolean; | ||
14 | +} | ||
15 | + | ||
16 | +/** | ||
17 | + * An interface that defines the properties of the RadioButton component. | ||
18 | + */ | ||
19 | +export interface RadioButtonProps extends FieldProps { | ||
20 | + options: Option[]; | ||
21 | +} | ||
22 | + | ||
23 | +/** | ||
24 | + * The RadioButton component. | ||
25 | + * | ||
26 | + * This is a wrapper around the Radio.Group component with optionType set to 'button' | ||
27 | + * which makes it easier to use in a Formily form. | ||
28 | + */ | ||
29 | +const RadioButton: React.FC<RadioButtonProps> = (props) => { | ||
30 | + const { name, title, options = [], validator = [], decoratorProps, componentProps } = props; | ||
31 | + | ||
32 | + return ( | ||
33 | + <div className={cx('global_radio_button')}> | ||
34 | + <Field | ||
35 | + {...props} | ||
36 | + name={name} | ||
37 | + title={title} | ||
38 | + dataSource={options} | ||
39 | + decorator={[ | ||
40 | + FormItem, | ||
41 | + { | ||
42 | + ...decoratorProps, | ||
43 | + }, | ||
44 | + ]} | ||
45 | + component={[ | ||
46 | + Radio.Group, | ||
47 | + { | ||
48 | + optionType: 'button', | ||
49 | + ...componentProps, | ||
50 | + componenttypename: 'Radio.Group', | ||
51 | + }, | ||
52 | + ]} | ||
53 | + validator={validator} | ||
54 | + /> | ||
55 | + </div> | ||
56 | + ); | ||
57 | +}; | ||
58 | + | ||
59 | +export default RadioButton; |
1 | +#root .global_radio { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-radio-group { | ||
12 | + .ant-radio-wrapper { | ||
13 | + .ant-radio { | ||
14 | + &-inner { | ||
15 | + border-color: #ebeef5; | ||
16 | + } | ||
17 | + } | ||
18 | + } | ||
19 | + } | ||
20 | + } | ||
21 | + } | ||
22 | + } | ||
23 | + } | ||
24 | +} |
packages/hr/antd_input/radio_group/index.tsx
0 → 100644
1 | +import { FormItem, Radio } from '@formily/antd-v5'; | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import cx from 'classnames'; | ||
4 | +import './index.less'; | ||
5 | +import { FieldProps } from '../../typings'; | ||
6 | + | ||
7 | +/** | ||
8 | + * An interface that defines an option in the RadioGroup component. | ||
9 | + */ | ||
10 | +interface Option { | ||
11 | + value: string | number; | ||
12 | + label: string; | ||
13 | + disabled?: boolean; | ||
14 | +} | ||
15 | + | ||
16 | +/** | ||
17 | + * An interface that defines the properties of the RadioGroup component. | ||
18 | + */ | ||
19 | +export interface RadioGroupProps extends FieldProps { | ||
20 | + options: Option[]; | ||
21 | +} | ||
22 | + | ||
23 | +/** | ||
24 | + * The RadioGroup component. | ||
25 | + * | ||
26 | + * This is a wrapper around the Radio.Group component that makes it easier to use in a Formily form. | ||
27 | + */ | ||
28 | +const RadioGroup: React.FC<RadioGroupProps> = (props) => { | ||
29 | + const { name, title, options = [], validator = [], decoratorProps, componentProps } = props; | ||
30 | + | ||
31 | + return ( | ||
32 | + <div className={cx('global_radio')}> | ||
33 | + <Field | ||
34 | + {...props} | ||
35 | + name={name} | ||
36 | + title={title} | ||
37 | + dataSource={options} | ||
38 | + decorator={[ | ||
39 | + FormItem, | ||
40 | + { | ||
41 | + ...decoratorProps, | ||
42 | + }, | ||
43 | + ]} | ||
44 | + component={[ | ||
45 | + Radio.Group, | ||
46 | + { | ||
47 | + ...componentProps, | ||
48 | + componenttypename: 'Radio.Group', | ||
49 | + }, | ||
50 | + ]} | ||
51 | + validator={validator} | ||
52 | + /> | ||
53 | + </div> | ||
54 | + ); | ||
55 | +}; | ||
56 | + | ||
57 | +export default RadioGroup; |
packages/hr/antd_input/range_date/index.less
0 → 100644
1 | +#root .global_rangedate { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-picker { | ||
12 | + border-color: #ebeef5; | ||
13 | + border-radius: 2px; | ||
14 | + padding: 6px 13px; | ||
15 | + | ||
16 | + &-input { | ||
17 | + > input::placeholder { | ||
18 | + color: #ccc; | ||
19 | + font-weight: 400; | ||
20 | + } | ||
21 | + } | ||
22 | + | ||
23 | + &-suffix { | ||
24 | + color: #606266; | ||
25 | + } | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | +} |
packages/hr/antd_input/range_date/index.tsx
0 → 100644
1 | +import React from 'react'; | ||
2 | +import { FormItem, DatePicker } from '@formily/antd-v5'; | ||
3 | +import { Field } from '@formily/react'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | +import moment from 'moment'; | ||
8 | + | ||
9 | +const { RangePicker } = DatePicker; | ||
10 | +const RangePickerWrap = (props: any) => { | ||
11 | + const { value, onChange, unix } = props; | ||
12 | + | ||
13 | + const [stateValue, setStateValue] = React.useState<any>([]); | ||
14 | + | ||
15 | + React.useEffect(() => { | ||
16 | + if (value && value.length && unix) { | ||
17 | + setStateValue([moment(value[0] * 1000), moment(value[1] * 1000)]); | ||
18 | + return; | ||
19 | + } | ||
20 | + | ||
21 | + setStateValue(value); | ||
22 | + }, [value]); | ||
23 | + | ||
24 | + const handleChange = (value: any) => { | ||
25 | + const [start, end] = value || []; | ||
26 | + | ||
27 | + if (!value) { | ||
28 | + onChange([]); | ||
29 | + return; | ||
30 | + } | ||
31 | + if (unix) { | ||
32 | + onChange([moment(start).unix(), moment(end).unix()]); | ||
33 | + } else { | ||
34 | + onChange(value); | ||
35 | + } | ||
36 | + }; | ||
37 | + | ||
38 | + return <RangePicker {...props} onChange={handleChange} value={stateValue} />; | ||
39 | +}; | ||
40 | + | ||
41 | +const RangeDate: React.FC<FieldProps> = (props) => { | ||
42 | + const { name, title, validator = [], format, decoratorProps, componentProps } = props; | ||
43 | + | ||
44 | + return ( | ||
45 | + <div className={cx('global_rangedate')}> | ||
46 | + <Field | ||
47 | + {...props} | ||
48 | + name={name} | ||
49 | + title={title} | ||
50 | + decorator={[ | ||
51 | + FormItem, | ||
52 | + { | ||
53 | + ...decoratorProps, | ||
54 | + }, | ||
55 | + ]} | ||
56 | + component={[ | ||
57 | + RangePickerWrap, | ||
58 | + { | ||
59 | + allowClear: true, | ||
60 | + componenttypename: 'DatePicker.RangePicker', | ||
61 | + format: format || ['YYYY/MM/DD', 'YYYY/MM/DD'], | ||
62 | + ...componentProps, | ||
63 | + }, | ||
64 | + ]} | ||
65 | + validator={validator} | ||
66 | + /> | ||
67 | + </div> | ||
68 | + ); | ||
69 | +}; | ||
70 | + | ||
71 | +export default RangeDate; |
1 | +#root .global_search { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-input-affix-wrapper { | ||
12 | + border-color: #f2f3f5; | ||
13 | + border-radius: 2px; | ||
14 | + padding: 6px 13px; | ||
15 | + background: #f2f3f5; | ||
16 | + | ||
17 | + > input::placeholder { | ||
18 | + color: #ccc; | ||
19 | + font-weight: 400; | ||
20 | + } | ||
21 | + | ||
22 | + .ant-input-prefix { | ||
23 | + font-size: 16px; | ||
24 | + color: rgba(0, 0, 0, 0.25); | ||
25 | + margin-inline-end: 10px; | ||
26 | + } | ||
27 | + | ||
28 | + .ant-input { | ||
29 | + background-color: #f2f3f5; | ||
30 | + } | ||
31 | + } | ||
32 | + } | ||
33 | + } | ||
34 | + } | ||
35 | + } | ||
36 | +} |
1 | +import { Field } from '@formily/react'; | ||
2 | +import { FormItem, Input } from '@formily/antd-v5'; | ||
3 | +import { SearchOutlined } from '@ant-design/icons'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | + | ||
8 | +const SearchInput: React.FC<FieldProps> = (props) => { | ||
9 | + const { name, validator = [], decoratorProps, componentProps } = props; | ||
10 | + | ||
11 | + return ( | ||
12 | + <div className={cx('global_search')}> | ||
13 | + <Field | ||
14 | + {...props} | ||
15 | + name={name} | ||
16 | + decorator={[ | ||
17 | + FormItem, | ||
18 | + { | ||
19 | + ...decoratorProps, | ||
20 | + }, | ||
21 | + ]} | ||
22 | + component={[ | ||
23 | + Input, | ||
24 | + { | ||
25 | + allowClear: true, | ||
26 | + prefix: <SearchOutlined />, | ||
27 | + ...componentProps, | ||
28 | + componenttypename: 'Input', | ||
29 | + }, | ||
30 | + ]} | ||
31 | + validator={validator} | ||
32 | + /> | ||
33 | + </div> | ||
34 | + ); | ||
35 | +}; | ||
36 | + | ||
37 | +export default SearchInput; |
packages/hr/antd_input/select/index.less
0 → 100644
1 | +#root .global_select { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-select { | ||
12 | + &-selector { | ||
13 | + height: 36px; | ||
14 | + border-color: #ebeef5; | ||
15 | + border-radius: 2px; | ||
16 | + padding-left: 13px; | ||
17 | + } | ||
18 | + | ||
19 | + &-arrow { | ||
20 | + color: #adaeb1; | ||
21 | + } | ||
22 | + | ||
23 | + &-selection-placeholder { | ||
24 | + color: #ccc; | ||
25 | + font-weight: 400; | ||
26 | + line-height: 36px; | ||
27 | + } | ||
28 | + } | ||
29 | + } | ||
30 | + } | ||
31 | + } | ||
32 | + } | ||
33 | +} |
packages/hr/antd_input/select/index.tsx
0 → 100644
1 | + | ||
2 | +import { FormItem, Select } from '@formily/antd-v5'; | ||
3 | +import { Field } from '@formily/react'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | + | ||
8 | +const SelectInput: React.FC<FieldProps> = (props) => { | ||
9 | + const { name, title, options = [], validator = [], decoratorProps, componentProps } = props; | ||
10 | + | ||
11 | + return ( | ||
12 | + <div className={cx('global_select')}> | ||
13 | + <Field | ||
14 | + {...props} | ||
15 | + name={name} | ||
16 | + title={title} | ||
17 | + dataSource={options} | ||
18 | + decorator={[ | ||
19 | + FormItem, | ||
20 | + { | ||
21 | + ...decoratorProps, | ||
22 | + }, | ||
23 | + ]} | ||
24 | + component={[ | ||
25 | + Select, | ||
26 | + { | ||
27 | + allowClear: true, | ||
28 | + showSearch: true, | ||
29 | + filterOption: (inputValue, option) => { | ||
30 | + console.log('option.label3333', option.label); | ||
31 | + let l = option.label + ''; | ||
32 | + | ||
33 | + return l && l.indexOf(inputValue) != -1; | ||
34 | + }, | ||
35 | + ...componentProps, | ||
36 | + componenttypename: 'Select', | ||
37 | + }, | ||
38 | + ]} | ||
39 | + validator={validator} | ||
40 | + /> | ||
41 | + </div> | ||
42 | + ); | ||
43 | +}; | ||
44 | + | ||
45 | +export default SelectInput; |
1 | +#root .global_textarea { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-input { | ||
12 | + padding: 0; | ||
13 | + | ||
14 | + &-affix-wrapper { | ||
15 | + border-color: #ebeef5; | ||
16 | + border-radius: 2px; | ||
17 | + padding: 6px 13px; | ||
18 | + | ||
19 | + &::placeholder { | ||
20 | + color: #ccc; | ||
21 | + font-weight: 400; | ||
22 | + } | ||
23 | + } | ||
24 | + } | ||
25 | + } | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | +} |
1 | + | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import { FormItem, Input } from '@formily/antd-v5'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | + | ||
8 | +const TextAreaInput: React.FC<FieldProps> = (props) => { | ||
9 | + const { name, title, validator = [], decoratorProps, componentProps } = props; | ||
10 | + | ||
11 | + return ( | ||
12 | + <div className={cx('global_textarea')}> | ||
13 | + <Field | ||
14 | + {...props} | ||
15 | + name={name} | ||
16 | + title={title} | ||
17 | + decorator={[FormItem, { ...decoratorProps }]} | ||
18 | + component={[ | ||
19 | + Input.TextArea, | ||
20 | + { | ||
21 | + allowClear: true, | ||
22 | + ...componentProps, | ||
23 | + componenttypename: 'Input.TextArea', | ||
24 | + }, | ||
25 | + ]} | ||
26 | + validator={validator} | ||
27 | + /> | ||
28 | + </div> | ||
29 | + ); | ||
30 | +}; | ||
31 | + | ||
32 | +export default TextAreaInput; |
packages/hr/antd_input/text_input/index.less
0 → 100644
1 | +#root .global_text { | ||
2 | + .ant-formily-item { | ||
3 | + &-label { | ||
4 | + font-weight: 500; | ||
5 | + color: #1d2129; | ||
6 | + } | ||
7 | + | ||
8 | + &-control { | ||
9 | + &-content { | ||
10 | + &-component { | ||
11 | + .ant-input-affix-wrapper { | ||
12 | + border-color: #ebeef5; | ||
13 | + border-radius: 2px; | ||
14 | + padding: 6px 13px; | ||
15 | + | ||
16 | + > input::placeholder { | ||
17 | + color: #ccc; | ||
18 | + font-weight: 400; | ||
19 | + } | ||
20 | + } | ||
21 | + } | ||
22 | + } | ||
23 | + } | ||
24 | + } | ||
25 | +} |
packages/hr/antd_input/text_input/index.tsx
0 → 100644
1 | + | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import { FormItem, Input } from '@formily/antd-v5'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | + | ||
8 | +const TextInput: React.FC<FieldProps> = (props) => { | ||
9 | + const { name, title, validator = [], decoratorProps, componentProps, ...args } = props; | ||
10 | + | ||
11 | + return ( | ||
12 | + <div className={cx('global_text')}> | ||
13 | + <Field | ||
14 | + {...props} | ||
15 | + name={name} | ||
16 | + title={title} | ||
17 | + {...args} | ||
18 | + decorator={[FormItem, { ...decoratorProps }]} | ||
19 | + component={[ | ||
20 | + Input, | ||
21 | + { | ||
22 | + allowClear: true, | ||
23 | + ...componentProps, | ||
24 | + componenttypename: 'Input', | ||
25 | + }, | ||
26 | + ]} | ||
27 | + validator={validator} | ||
28 | + /> | ||
29 | + </div> | ||
30 | + ); | ||
31 | +}; | ||
32 | + | ||
33 | +export default TextInput; |
1 | + | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import { FormItem } from '@formily/antd-v5'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | +import UploadFiles from '@/hr/upload_file'; | ||
8 | + | ||
9 | +const InputUpload: React.FC<FieldProps> = (props) => { | ||
10 | + const { name, title, validator = [], decoratorProps, componentProps } = props; | ||
11 | + | ||
12 | + return ( | ||
13 | + <div className={cx('global_textarea')}> | ||
14 | + <Field | ||
15 | + {...props} | ||
16 | + name={name} | ||
17 | + title={title} | ||
18 | + decorator={[FormItem, { ...decoratorProps }]} | ||
19 | + component={[ | ||
20 | + UploadFiles, | ||
21 | + { | ||
22 | + ...componentProps, | ||
23 | + }, | ||
24 | + ]} | ||
25 | + validator={validator} | ||
26 | + /> | ||
27 | + </div> | ||
28 | + ); | ||
29 | +}; | ||
30 | + | ||
31 | +export default InputUpload; |
1 | + | ||
2 | +import { Field } from '@formily/react'; | ||
3 | +import { FormItem } from '@formily/antd-v5'; | ||
4 | +import cx from 'classnames'; | ||
5 | +import './index.less'; | ||
6 | +import { FieldProps } from '../../typings'; | ||
7 | +import UploadFiles from '@/hr/upload_file'; | ||
8 | + | ||
9 | +const UploadWrap: React.FC<FieldProps> = (props) => { | ||
10 | + const { name, title, validator = [], decoratorProps, componentProps } = props; | ||
11 | + | ||
12 | + return ( | ||
13 | + <div className={cx('global_upload_wrap')}> | ||
14 | + <Field | ||
15 | + {...props} | ||
16 | + name={name} | ||
17 | + title={title} | ||
18 | + decorator={[FormItem, { ...decoratorProps }]} | ||
19 | + component={[ | ||
20 | + UploadFiles, | ||
21 | + { | ||
22 | + ...componentProps, | ||
23 | + componenttypename: 'UploadFiles', | ||
24 | + }, | ||
25 | + ]} | ||
26 | + validator={validator} | ||
27 | + /> | ||
28 | + </div> | ||
29 | + ); | ||
30 | +}; | ||
31 | + | ||
32 | +export default UploadWrap; |
packages/hr/antd_input/uploader/index.less
0 → 100644
packages/hr/braft_editor/index.d.ts
0 → 100644
1 | +import React, { ReactElement } from 'react'; | ||
2 | +import 'braft-editor/dist/index.css'; | ||
3 | +import './index.less'; | ||
4 | +import { IFieldProps } from '@formily/core'; | ||
5 | +interface P extends IFieldProps { | ||
6 | + name: string | [string, string]; | ||
7 | + title?: string | ReactElement; | ||
8 | + validator?: any; | ||
9 | + [props: string]: any; | ||
10 | + decoratorProps?: { | ||
11 | + [props: string]: any; | ||
12 | + }; | ||
13 | + componentProps?: { | ||
14 | + [props: string]: any; | ||
15 | + }; | ||
16 | +} | ||
17 | +declare const BraftDeitor: React.FC<P>; | ||
18 | +export default BraftDeitor; |
packages/hr/braft_editor/index.less
0 → 100644
1 | +.editor_warp { | ||
2 | + border-radius: 6px; | ||
3 | + margin-bottom: 30px; | ||
4 | + | ||
5 | + .ant-formily-item { | ||
6 | + &-label { | ||
7 | + font-weight: 500; | ||
8 | + color: #1d2129; | ||
9 | + } | ||
10 | + | ||
11 | + .editor { | ||
12 | + border: 1px solid #ebeef5; | ||
13 | + | ||
14 | + .bf-container { | ||
15 | + .public-DraftEditor-content { | ||
16 | + padding: 6px 13px; | ||
17 | + } | ||
18 | + | ||
19 | + .bf-content { | ||
20 | + color: #1d2129; | ||
21 | + font-size: 14px; | ||
22 | + | ||
23 | + .public-DraftEditorPlaceholder-root { | ||
24 | + top: 6px; | ||
25 | + left: 13px; | ||
26 | + } | ||
27 | + | ||
28 | + .public-DraftEditorPlaceholder-inner { | ||
29 | + color: #ccc; | ||
30 | + font-weight: 400; | ||
31 | + font-size: 14px; | ||
32 | + | ||
33 | + > div > p { | ||
34 | + margin-block-start: 0; | ||
35 | + margin-block-end: 3px; | ||
36 | + } | ||
37 | + } | ||
38 | + } | ||
39 | + } | ||
40 | + } | ||
41 | + } | ||
42 | +} |
packages/hr/braft_editor/index.tsx
0 → 100644
1 | +// https://www.yuque.com/braft-editor/be/lzwpnr#ecff77a8 富文本编辑器 简介说明 | ||
2 | + | ||
3 | +import React, { ReactElement } from 'react'; | ||
4 | +// 引入编辑器组件 | ||
5 | +import BraftEditor, { BraftEditorProps, EditorState } from 'braft-editor'; | ||
6 | +import 'braft-editor/dist/index.css'; | ||
7 | +import cx from 'classnames'; | ||
8 | +import './index.less'; | ||
9 | +import { FormItem } from '@formily/antd-v5'; | ||
10 | +import { Field } from '@formily/react'; | ||
11 | +import { IFieldProps } from '@formily/core'; | ||
12 | + | ||
13 | +interface Props extends BraftEditorProps { | ||
14 | + contentStyle?: { [props: string]: string | number }; | ||
15 | + changeEditor?: (editorState: EditorState) => void; | ||
16 | +} | ||
17 | + | ||
18 | +const Editor: React.FC<Props> = (props) => { | ||
19 | + const { | ||
20 | + value = null, | ||
21 | + changeEditor, | ||
22 | + controls = [], | ||
23 | + excludeControls = [], | ||
24 | + extendControls = [], | ||
25 | + contentStyle = { width: 600, height: 120 }, | ||
26 | + placeholder, | ||
27 | + } = props; | ||
28 | + | ||
29 | + const { width, height } = contentStyle; | ||
30 | + let myContentStyle = contentStyle | ||
31 | + ? contentStyle | ||
32 | + : { | ||
33 | + height: '120px', | ||
34 | + fontSize: '14px', | ||
35 | + }; | ||
36 | + | ||
37 | + return ( | ||
38 | + <div | ||
39 | + className={cx('editor')} | ||
40 | + style={{ | ||
41 | + width, | ||
42 | + height, | ||
43 | + }} | ||
44 | + > | ||
45 | + <BraftEditor | ||
46 | + value={BraftEditor.createEditorState(value)} | ||
47 | + stripPastedStyles={true} | ||
48 | + contentStyle={myContentStyle} | ||
49 | + onBlur={(event: EditorState) => { | ||
50 | + if (changeEditor) { | ||
51 | + changeEditor(event.toHTML()); | ||
52 | + } | ||
53 | + }} | ||
54 | + placeholder={placeholder} | ||
55 | + controls={controls} //控件列表 | ||
56 | + excludeControls={excludeControls} //不展示的 控件列表 | ||
57 | + extendControls={extendControls} //自定义控件 | ||
58 | + /> | ||
59 | + </div> | ||
60 | + ); | ||
61 | +}; | ||
62 | + | ||
63 | +interface P extends IFieldProps { | ||
64 | + name: string | [string, string]; | ||
65 | + title?: string | ReactElement; | ||
66 | + validator?: any; | ||
67 | + [props: string]: any; | ||
68 | + decoratorProps?: { | ||
69 | + [props: string]: any; | ||
70 | + }; | ||
71 | + componentProps?: { | ||
72 | + [props: string]: any; | ||
73 | + }; | ||
74 | +} | ||
75 | + | ||
76 | +const BraftDeitor: React.FC<P> = (props) => { | ||
77 | + const { name, title, validator, decoratorProps, componentProps } = props; | ||
78 | + | ||
79 | + return ( | ||
80 | + <div className={cx('editor_warp')}> | ||
81 | + <Field | ||
82 | + name={name} | ||
83 | + title={title} | ||
84 | + validator={validator} | ||
85 | + decorator={[FormItem, decoratorProps]} | ||
86 | + component={[Editor, componentProps]} | ||
87 | + /> | ||
88 | + </div> | ||
89 | + ); | ||
90 | +}; | ||
91 | + | ||
92 | +export default BraftDeitor; |
packages/hr/bread_crumb/bread.less
0 → 100644
packages/hr/bread_crumb/index.d.ts
0 → 100644
packages/hr/bread_crumb/index.tsx
0 → 100644
1 | +import React from 'react'; | ||
2 | +import { Breadcrumb as MyBreadcrumb } from 'antd'; | ||
3 | +import { ItemType } from 'antd/lib/breadcrumb/Breadcrumb'; | ||
4 | +import './bread.less'; | ||
5 | + | ||
6 | +interface P { | ||
7 | + separator?: string; | ||
8 | + items: ItemType[]; | ||
9 | +} | ||
10 | + | ||
11 | +const Breadcrumb: React.FC<P> = (props) => { | ||
12 | + const { separator = '>', items = [] } = props; | ||
13 | + | ||
14 | + return ( | ||
15 | + <div className={'bread_warp'}> | ||
16 | + <MyBreadcrumb separator={separator} items={items} /> | ||
17 | + </div> | ||
18 | + ); | ||
19 | +}; | ||
20 | + | ||
21 | +export default Breadcrumb; |
packages/hr/button_radio/index.d.ts
0 → 100644
1 | +import React from 'react'; | ||
2 | +import { ButtonProps } from 'antd'; | ||
3 | +import type { SpaceProps } from 'antd'; | ||
4 | +export interface ButtonRadioProps extends SpaceProps { | ||
5 | + className?: string; | ||
6 | + prefixCls?: string; | ||
7 | + buttonProps?: ButtonProps; | ||
8 | + onChange?: (value: any) => void; | ||
9 | + value?: any; | ||
10 | + [props: string]: any; | ||
11 | +} | ||
12 | +declare const ButtonRadio: React.FC<ButtonRadioProps>; | ||
13 | +export default ButtonRadio; |
packages/hr/button_radio/index.tsx
0 → 100644
1 | +import React from 'react'; | ||
2 | +import { Button, Space, ButtonProps } from 'antd'; | ||
3 | +import type { SpaceProps } from 'antd'; | ||
4 | +export interface ButtonRadioProps extends SpaceProps { | ||
5 | + className?: string; | ||
6 | + prefixCls?: string; | ||
7 | + buttonProps?: ButtonProps; | ||
8 | + onChange?: (value: any) => void; | ||
9 | + value?: any; | ||
10 | + [props: string]: any; | ||
11 | +} | ||
12 | + | ||
13 | +const ButtonRadio: React.FC<ButtonRadioProps> = (props) => { | ||
14 | + const { options = [], buttonProps = {}, value, onChange } = props; | ||
15 | + | ||
16 | + return ( | ||
17 | + <Space {...props}> | ||
18 | + {options.map( | ||
19 | + ( | ||
20 | + option: { | ||
21 | + value: any; | ||
22 | + label: | ||
23 | + | string | ||
24 | + | number | ||
25 | + | boolean | ||
26 | + | React.ReactElement<any, string | React.JSXElementConstructor<any>> | ||
27 | + | React.ReactFragment | ||
28 | + | React.ReactPortal | ||
29 | + | null | ||
30 | + | undefined; | ||
31 | + }, | ||
32 | + i: React.Key | null | undefined, | ||
33 | + ) => { | ||
34 | + const type = option.value === value ? 'primary' : 'default'; | ||
35 | + return ( | ||
36 | + <Button | ||
37 | + key={i} | ||
38 | + {...buttonProps} | ||
39 | + type={type} | ||
40 | + onClick={() => { | ||
41 | + onChange && onChange(option.value); | ||
42 | + }} | ||
43 | + > | ||
44 | + {option.label} | ||
45 | + </Button> | ||
46 | + ); | ||
47 | + }, | ||
48 | + )} | ||
49 | + </Space> | ||
50 | + ); | ||
51 | +}; | ||
52 | + | ||
53 | +export default ButtonRadio; |
packages/hr/form_modal/index.d.ts
0 → 100644
1 | +import React from 'react'; | ||
2 | +import { ModalProps } from 'antd'; | ||
3 | +interface Props extends ModalProps { | ||
4 | + handleOk?: (values: { | ||
5 | + [props: string]: string; | ||
6 | + }, e: React.MouseEvent<HTMLButtonElement>) => void; | ||
7 | + handleCancel?: (e: React.MouseEvent<HTMLButtonElement>) => void; | ||
8 | + initialValues?: { | ||
9 | + props?: string; | ||
10 | + }; | ||
11 | + open: boolean; | ||
12 | +} | ||
13 | +declare const FormModal: React.FC<Props>; | ||
14 | +export default FormModal; |
packages/hr/form_modal/index.tsx
0 → 100644
1 | +import React, { useEffect } from 'react'; | ||
2 | +import { Modal, ModalProps } from 'antd'; | ||
3 | +import { FormProvider } from '@formily/react'; | ||
4 | +import { createForm } from '@formily/core'; | ||
5 | + | ||
6 | +interface Props extends ModalProps { | ||
7 | + handleOk?: (values: { [props: string]: string }, e: React.MouseEvent<HTMLButtonElement>) => void; | ||
8 | + handleCancel?: (e: React.MouseEvent<HTMLButtonElement>) => void; | ||
9 | + initialValues?: { props?: string }; | ||
10 | + open: boolean; | ||
11 | +} | ||
12 | + | ||
13 | +const FormModal: React.FC<Props> = (props) => { | ||
14 | + const form = createForm(); | ||
15 | + const { | ||
16 | + initialValues, | ||
17 | + title = '我是弹框标题', | ||
18 | + handleOk, | ||
19 | + handleCancel, | ||
20 | + children = [], | ||
21 | + open, | ||
22 | + ...args | ||
23 | + } = props; | ||
24 | + const items = React.Children.toArray(children); | ||
25 | + | ||
26 | + useEffect(() => { | ||
27 | + form.setInitialValues(initialValues); | ||
28 | + }); | ||
29 | + | ||
30 | + const onOK = (e: React.MouseEvent<HTMLButtonElement>) => { | ||
31 | + if (handleOk) { | ||
32 | + form.submit().then((values: any) => { | ||
33 | + handleOk(values, e); | ||
34 | + }); | ||
35 | + | ||
36 | + return; | ||
37 | + } | ||
38 | + }; | ||
39 | + | ||
40 | + const onCancel = (e: React.MouseEvent<HTMLButtonElement>) => { | ||
41 | + if (handleCancel) { | ||
42 | + handleCancel(e); | ||
43 | + | ||
44 | + return; | ||
45 | + } | ||
46 | + }; | ||
47 | + | ||
48 | + return ( | ||
49 | + <Modal | ||
50 | + title={title} | ||
51 | + open={open} | ||
52 | + onOk={onOK} | ||
53 | + onCancel={onCancel} | ||
54 | + maskClosable={false} | ||
55 | + centered | ||
56 | + {...args} | ||
57 | + getContainer={false} | ||
58 | + > | ||
59 | + <FormProvider form={form}>{React.Children.map(items, (item: any) => item)}</FormProvider> | ||
60 | + </Modal> | ||
61 | + ); | ||
62 | +}; | ||
63 | + | ||
64 | +export default FormModal; |
packages/hr/image/index.d.ts
0 → 100644
packages/hr/image/index.tsx
0 → 100644
1 | +import { ImageProps, Image as MyImage } from 'antd'; | ||
2 | + | ||
3 | +interface Props extends ImageProps { | ||
4 | + imgValues?: string[]; | ||
5 | + show_image?: boolean; | ||
6 | + setShowImage?: (value: any) => void; | ||
7 | +} | ||
8 | + | ||
9 | +const Image: React.FC<Props> = (props) => { | ||
10 | + const { src, style, preview, imgValues = [], show_image, setShowImage, ...args } = props; | ||
11 | + let my_preview = preview | ||
12 | + ? preview | ||
13 | + : { | ||
14 | + visible: show_image, | ||
15 | + src: src, | ||
16 | + }; | ||
17 | + | ||
18 | + return ( | ||
19 | + <> | ||
20 | + <MyImage | ||
21 | + width={200} | ||
22 | + style={style} | ||
23 | + src={src} | ||
24 | + preview={my_preview} | ||
25 | + {...args} | ||
26 | + onClick={() => setShowImage && setShowImage(!show_image)} | ||
27 | + /> | ||
28 | + <div style={{ display: 'none' }}> | ||
29 | + <MyImage.PreviewGroup | ||
30 | + preview={{ | ||
31 | + visible: show_image, | ||
32 | + onVisibleChange: (vis) => setShowImage && setShowImage(vis), | ||
33 | + }} | ||
34 | + > | ||
35 | + {imgValues.map((data: string, index: number) => ( | ||
36 | + <MyImage src={data} key={index} /> | ||
37 | + ))} | ||
38 | + </MyImage.PreviewGroup> | ||
39 | + </div> | ||
40 | + </> | ||
41 | + ); | ||
42 | +}; | ||
43 | + | ||
44 | +export default Image; |
packages/hr/map_select/index.d.ts
0 → 100644
packages/hr/map_select/index.less
0 → 100644
packages/hr/map_select/index.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | ||
2 | +import { Input, Space, Select, Divider, Cascader } from 'antd'; | ||
3 | +import AMapLoader from '@amap/amap-jsapi-loader'; | ||
4 | +import './index.less'; | ||
5 | +import areaData from '@/utils/areaData'; | ||
6 | +import { getAreaData, getAreaNamesByCodes } from '@/utils/format'; | ||
7 | +import $ from 'jquery'; | ||
8 | +// 获取平铺的地区数据 | ||
9 | +const areaDatas = getAreaData(areaData); | ||
10 | + | ||
11 | +declare global { | ||
12 | + interface Window { | ||
13 | + _AMapSecurityConfig: any; | ||
14 | + AMapUI: any; | ||
15 | + AMap: any; | ||
16 | + } | ||
17 | +} | ||
18 | + | ||
19 | +const { Search } = Input; | ||
20 | + | ||
21 | +window._AMapSecurityConfig = { | ||
22 | + securityJsCode: 'f4e73616d1028a16dad9556a0d493571', | ||
23 | +}; | ||
24 | + | ||
25 | +const MapRender: React.FC = (props: any) => { | ||
26 | + const [map, setMap] = useState<any>(null); | ||
27 | + const [marker, setMarker] = useState<any>(null); | ||
28 | + const [infoWindow, setInfoWindow] = useState<any>(null); | ||
29 | + const [mapVisble, setMapVisble] = useState<boolean>(true); | ||
30 | + const { poiPicker, setPoiPicker, open, setSelectCode, addressName } = props; | ||
31 | + const initMap = async () => { | ||
32 | + const AMap = await AMapLoader.load({ | ||
33 | + key: '6b7ba7695c5c2c76d10610a3be45a0f1', // 申请好的Web端开发者Key,首次调用 load 时必填 | ||
34 | + version: '1.4.15', // 指定要加载的 JS API 的版本,缺省时默认为 1.4.15 | ||
35 | + plugins: ['AMap.Scale'], // 需要使用的的插件列表,如比例尺'AMap.Scale'等 | ||
36 | + AMapUI: { | ||
37 | + version: '1.0', | ||
38 | + }, | ||
39 | + }); | ||
40 | + | ||
41 | + if (AMap) { | ||
42 | + if (!map) { | ||
43 | + const tempMap = new AMap.Map(addressName + 'map'); | ||
44 | + | ||
45 | + setMap(tempMap); | ||
46 | + tempMap.addControl(new AMap.Scale()); | ||
47 | + } | ||
48 | + | ||
49 | + if (!marker) { | ||
50 | + setMarker(new AMap.Marker()); | ||
51 | + } | ||
52 | + if (!infoWindow) { | ||
53 | + setInfoWindow( | ||
54 | + new AMap.InfoWindow({ | ||
55 | + offset: new AMap.Pixel(0, -20), | ||
56 | + }), | ||
57 | + ); | ||
58 | + } | ||
59 | + } | ||
60 | + window.AMapUI.setDomLibrary($); | ||
61 | + if (window.AMapUI) { | ||
62 | + window.AMapUI.loadUI(['misc/PoiPicker', 'misc/PositionPicker'], function (PoiPicker: any) { | ||
63 | + if (!poiPicker) { | ||
64 | + const tempPoiPicker = new PoiPicker({ | ||
65 | + //city:'北京', | ||
66 | + input: addressName + 'search', | ||
67 | + }); | ||
68 | + setPoiPicker(tempPoiPicker); | ||
69 | + } | ||
70 | + }); | ||
71 | + } | ||
72 | + }; | ||
73 | + | ||
74 | + const initWindowMap = async () => { | ||
75 | + if (!map) { | ||
76 | + const tempMap = new window.AMap.Map(addressName + 'map', { | ||
77 | + zoom: 10, | ||
78 | + }); | ||
79 | + setMap(tempMap); | ||
80 | + } | ||
81 | + if (!marker) { | ||
82 | + // @ts-ignore | ||
83 | + setMarker(new window.AMap.Marker()); | ||
84 | + } | ||
85 | + if (!infoWindow) { | ||
86 | + setInfoWindow( | ||
87 | + new window.AMap.InfoWindow({ | ||
88 | + offset: new window.AMap.Pixel(0, -20), | ||
89 | + }), | ||
90 | + ); | ||
91 | + } | ||
92 | + if (window.AMapUI) { | ||
93 | + window.AMapUI.loadUI(['misc/PoiPicker', 'misc/PositionPicker'], function (PoiPicker: any) { | ||
94 | + if (!poiPicker) { | ||
95 | + const tempPoiPicker = new PoiPicker({ | ||
96 | + //city:'北京', | ||
97 | + input: addressName + 'search', | ||
98 | + }); | ||
99 | + setPoiPicker(tempPoiPicker); | ||
100 | + } | ||
101 | + }); | ||
102 | + } | ||
103 | + }; | ||
104 | + | ||
105 | + useEffect(() => { | ||
106 | + const search = document.getElementById(addressName + 'search'); | ||
107 | + search?.focus(); | ||
108 | + }, [document.getElementById(addressName + 'search')]); | ||
109 | + | ||
110 | + useEffect(() => { | ||
111 | + if (document.getElementById(addressName + 'map')) { | ||
112 | + if (window.AMap) { | ||
113 | + initWindowMap(); | ||
114 | + } else { | ||
115 | + initMap(); | ||
116 | + } | ||
117 | + } else { | ||
118 | + setMapVisble(!mapVisble); | ||
119 | + } | ||
120 | + if (poiPicker) { | ||
121 | + //初始化poiPicker | ||
122 | + poiPicker.on('poiPicked', function (poiResult: any) { | ||
123 | + const { onChange, setOpen } = props; | ||
124 | + const poi = poiResult.item; | ||
125 | + const { adcode }: { adcode: string } = poi; | ||
126 | + let provideCode = adcode.substring(0, 2) + '0000'; | ||
127 | + let cityCode = adcode.substring(0, 4) + '00'; | ||
128 | + let areaCode = adcode; | ||
129 | + map.clearMap(); | ||
130 | + marker.setMap(map); | ||
131 | + infoWindow.setMap(map); | ||
132 | + | ||
133 | + marker.setPosition(poi.location); | ||
134 | + infoWindow.setPosition(poi.location); | ||
135 | + | ||
136 | + // 根据code获取省市区名称 | ||
137 | + const info = getAreaNamesByCodes([provideCode, cityCode, areaCode], areaDatas); | ||
138 | + // infoWindow.setContent( | ||
139 | + // 'POI信息: <pre>' + JSON.stringify(info, null, 2) + '</pre>', | ||
140 | + // ); | ||
141 | + // infoWindow.open(map, marker.getPosition()); | ||
142 | + map.setCenter(marker.getPosition()); | ||
143 | + | ||
144 | + const { location = {}, address = '' } = poi; | ||
145 | + const { lng, lat } = location; | ||
146 | + const [provide, city, district] = info; | ||
147 | + | ||
148 | + if (onChange) { | ||
149 | + // 给表单组件赋值 | ||
150 | + onChange({ | ||
151 | + address, | ||
152 | + province_code: provideCode, | ||
153 | + city_code: cityCode, | ||
154 | + district_code: areaCode, | ||
155 | + provide, | ||
156 | + city, | ||
157 | + district, | ||
158 | + lng: lng + '', | ||
159 | + ltg: lat + '', | ||
160 | + }); | ||
161 | + } | ||
162 | + setSelectCode([provideCode, cityCode, areaCode]); | ||
163 | + setOpen(false); | ||
164 | + }); | ||
165 | + } | ||
166 | + }, [open, poiPicker, mapVisble]); | ||
167 | + | ||
168 | + return <div className="map" id={addressName + 'map'}></div>; | ||
169 | +}; | ||
170 | + | ||
171 | +const MapSelect = (props: any) => { | ||
172 | + const { value = {}, addressName = '', cascaderStyle = {}, selectStyle = {} } = props; | ||
173 | + const [poiPicker, setPoiPicker] = useState<any>(null); | ||
174 | + const [open, setOpen] = useState(false); | ||
175 | + const [searchValue, setSearchValue] = useState(''); | ||
176 | + const [selectCode, setSelectCode] = useState<any>([]); | ||
177 | + const { province_code, city_code, district_code } = value; | ||
178 | + | ||
179 | + useEffect(() => { | ||
180 | + if (province_code && city_code && district_code) { | ||
181 | + setSelectCode([province_code, city_code, district_code]); | ||
182 | + } | ||
183 | + }, [district_code]); | ||
184 | + | ||
185 | + useEffect(() => { | ||
186 | + setSearchValue(value?.address); | ||
187 | + }, [value?.address]); | ||
188 | + | ||
189 | + return ( | ||
190 | + <Space.Compact style={{ width: '100%' }}> | ||
191 | + <Cascader | ||
192 | + options={areaData} | ||
193 | + style={cascaderStyle} | ||
194 | + value={selectCode} | ||
195 | + placeholder="省/市/区" | ||
196 | + disabled | ||
197 | + /> | ||
198 | + <Select | ||
199 | + open={open} | ||
200 | + value={value?.address} | ||
201 | + dropdownMatchSelectWidth={false} | ||
202 | + style={selectStyle} | ||
203 | + placeholder="请输入关键字选取地点" | ||
204 | + onDropdownVisibleChange={(isopen) => { | ||
205 | + if (isopen) { | ||
206 | + setOpen(true); | ||
207 | + return; | ||
208 | + } | ||
209 | + setOpen(false); | ||
210 | + }} | ||
211 | + dropdownRender={() => ( | ||
212 | + <div style={{ overflow: 'auto' }}> | ||
213 | + <Search | ||
214 | + id={addressName + 'search'} | ||
215 | + placeholder="请输入关键字选取地点" | ||
216 | + value={searchValue} | ||
217 | + onPressEnter={(e: any) => { | ||
218 | + if (poiPicker && e.target.value) { | ||
219 | + poiPicker.searchByKeyword(e.target.value); | ||
220 | + } | ||
221 | + setSearchValue(e.target.value); | ||
222 | + }} | ||
223 | + onChange={(e) => { | ||
224 | + setSearchValue(e.target.value); | ||
225 | + }} | ||
226 | + // onSearch={(value) => {}} | ||
227 | + /> | ||
228 | + <Divider style={{ margin: '8px 0' }} /> | ||
229 | + <MapRender | ||
230 | + {...props} | ||
231 | + open={open} | ||
232 | + setOpen={setOpen} | ||
233 | + poiPicker={poiPicker} | ||
234 | + setPoiPicker={setPoiPicker} | ||
235 | + setSelectCode={setSelectCode} | ||
236 | + /> | ||
237 | + </div> | ||
238 | + )} | ||
239 | + /> | ||
240 | + </Space.Compact> | ||
241 | + ); | ||
242 | +}; | ||
243 | + | ||
244 | +export default MapSelect; |
packages/hr/modal_confirm/index.d.ts
0 → 100644
packages/hr/modal_confirm/index.tsx
0 → 100644
1 | +import { Modal, ModalFuncProps } from 'antd'; | ||
2 | +import { ExclamationCircleFilled } from '@ant-design/icons'; | ||
3 | +const { confirm } = Modal; | ||
4 | + | ||
5 | +const showPropsConfirm = (props: ModalFuncProps) => { | ||
6 | + const { title, icon, content, okText, okType, okButtonProps, cancelText, onOk, onCancel } = props; | ||
7 | + | ||
8 | + confirm({ | ||
9 | + title: title, | ||
10 | + icon: icon || <ExclamationCircleFilled />, | ||
11 | + content: content, | ||
12 | + okText: okText, | ||
13 | + okType: okType, | ||
14 | + okButtonProps: okButtonProps, //是个对象,其中disabled属性就是在这里设置 | ||
15 | + cancelText: cancelText, | ||
16 | + onOk: onOk, | ||
17 | + onCancel: onCancel, | ||
18 | + }); | ||
19 | +}; | ||
20 | + | ||
21 | +export default showPropsConfirm; |
packages/hr/pagination/index.less
0 → 100644
packages/hr/pagination/pagination.tsx
0 → 100644
1 | +import { Pagination } from 'antd'; | ||
2 | + | ||
3 | +interface paginationProps { | ||
4 | + [props: string]: any; | ||
5 | + onChange: Function; | ||
6 | +} | ||
7 | + | ||
8 | +const PaginationWrap = (props: paginationProps) => { | ||
9 | + const { pagination, changePagination, onChange, ...rest } = props; | ||
10 | + | ||
11 | + const { offset, limit = 10, total_count } = pagination; | ||
12 | + | ||
13 | + // 分页改变 | ||
14 | + const onPaginationChange = (page: number, limit: number) => { | ||
15 | + changePagination({ | ||
16 | + ...pagination, | ||
17 | + offset: (page - 1) * (limit || 10), | ||
18 | + limit: limit, | ||
19 | + }); | ||
20 | + onChange({ | ||
21 | + offset: (page - 1) * (limit || 10), | ||
22 | + limit: limit, | ||
23 | + }); | ||
24 | + }; | ||
25 | + | ||
26 | + return ( | ||
27 | + <Pagination | ||
28 | + style={{ textAlign: 'right', marginTop: '16px' }} | ||
29 | + current={(offset && Math.floor(offset / limit) + 1) || 1} | ||
30 | + pageSize={limit ? limit : 10} | ||
31 | + total={total_count} | ||
32 | + showTotal={(total) => `共 ${total} 条`} | ||
33 | + showSizeChanger={false} | ||
34 | + onChange={(page: number, pageSize: number) => { | ||
35 | + onPaginationChange(page, pageSize); | ||
36 | + }} | ||
37 | + {...rest} | ||
38 | + // pageSizeOptions={['10', '25', '50', '100']} | ||
39 | + /> | ||
40 | + ); | ||
41 | +}; | ||
42 | + | ||
43 | +export default PaginationWrap; |
packages/hr/search/index.less
0 → 100644
1 | +.search-component { | ||
2 | + display: flex; | ||
3 | + align-items: center; | ||
4 | + // justify-content: space-between; | ||
5 | + height: 72px; | ||
6 | + background: #ffffff; | ||
7 | + border-radius: 0px 0px 8px 8px; | ||
8 | + padding: 30px; | ||
9 | + margin-bottom: 20px; | ||
10 | + justify-content: space-between; | ||
11 | +} | ||
12 | + | ||
13 | +.left-section { | ||
14 | + display: flex; | ||
15 | + align-items: center; | ||
16 | + overflow: hidden; | ||
17 | + .ant-formily-item-feedback-layout-loose { | ||
18 | + margin-bottom: 0 !important; | ||
19 | + } | ||
20 | +} | ||
21 | + | ||
22 | +.left-item { | ||
23 | + margin-bottom: 5px; | ||
24 | +} | ||
25 | + | ||
26 | +.right-section { | ||
27 | + display: flex; | ||
28 | + align-items: center; | ||
29 | + position: relative; | ||
30 | + margin-left: 10px; | ||
31 | + .section-search { | ||
32 | + .ant-formily-item-feedback-layout-loose { | ||
33 | + margin-bottom: 0 !important; | ||
34 | + } | ||
35 | + } | ||
36 | +} | ||
37 | + | ||
38 | +.more-categories-button { | ||
39 | + margin-left: 10px; | ||
40 | +} | ||
41 | + | ||
42 | +.btn { | ||
43 | + display: flex; | ||
44 | + align-items: center; | ||
45 | + justify-content: flex-end; | ||
46 | + margin-top: 20px; | ||
47 | +} | ||
48 | + | ||
49 | +.popups { | ||
50 | + padding: 20px; | ||
51 | + width: 332px; | ||
52 | + max-width: 500px; | ||
53 | + | ||
54 | + .ant-tooltip-inner { | ||
55 | + padding: 20px; | ||
56 | + right: 100px; | ||
57 | + } | ||
58 | +} | ||
59 | + | ||
60 | +.more { | ||
61 | + margin-left: 20px; | ||
62 | + font-size: 14px; | ||
63 | + font-weight: 400; | ||
64 | + color: #303133; | ||
65 | +} | ||
66 | + | ||
67 | +.img { | ||
68 | + margin-left: 7px; | ||
69 | +} |
packages/hr/search/search.tsx
0 → 100644
1 | +import { ReactElement, useEffect, useMemo, useState } from 'react'; | ||
2 | +import { Space, Button, Tooltip, Tag } from 'antd'; | ||
3 | +import './index.less'; | ||
4 | +import { FormProvider, VoidField } from '@formily/react'; | ||
5 | +import { createForm, onFieldValueChange, Field } from '@formily/core'; | ||
6 | +import { ossAddress } from '@/utils/ossAddress'; | ||
7 | +import { useDebounce } from '@/utils/format'; | ||
8 | +import moment from 'moment'; | ||
9 | +export interface SearchWrapProps { | ||
10 | + leftSectionComponents?: ReactElement[]; | ||
11 | + searchInput?: ReactElement; | ||
12 | + popupContent?: ReactElement[]; | ||
13 | + searchChange?: Function; | ||
14 | + getFormInstance?: Function; | ||
15 | + moreCategories?: Boolean; | ||
16 | + ProForm?: any; | ||
17 | +} | ||
18 | + | ||
19 | +interface SearchChooseProps { | ||
20 | + name: string; | ||
21 | + title: string; | ||
22 | + value: string; | ||
23 | + path: any; | ||
24 | + address: any; | ||
25 | +} | ||
26 | + | ||
27 | +const _getValueLable = (field: Field) => { | ||
28 | + const { title, value, dataSource, props, path, address, componentProps } = field; | ||
29 | + const { name } = props; | ||
30 | + | ||
31 | + const { componenttypename, format } = componentProps; | ||
32 | + const labelMap: any = { | ||
33 | + name, | ||
34 | + path, | ||
35 | + address, | ||
36 | + title: title ? title : '', | ||
37 | + }; | ||
38 | + const getValue = (key: any) => { | ||
39 | + const dataSourceMap: Record<string, string> = {}; | ||
40 | + dataSource.map((item) => { | ||
41 | + if (item && item.value) { | ||
42 | + dataSourceMap[item.value] = item.label; | ||
43 | + } | ||
44 | + }); | ||
45 | + return dataSourceMap[key]; | ||
46 | + }; | ||
47 | + switch (componenttypename) { | ||
48 | + case 'Radio.Group': | ||
49 | + labelMap.value = getValue(value); | ||
50 | + break; | ||
51 | + case 'Select': | ||
52 | + console.log(value, 'selectValue'); | ||
53 | + | ||
54 | + case 'Checkbox.Group': | ||
55 | + const valArray: any[] = []; | ||
56 | + if (value && value.length > 0 && typeof value != 'string') { | ||
57 | + value.map((val: any) => { | ||
58 | + valArray.push(getValue(val)); | ||
59 | + }); | ||
60 | + labelMap.value = valArray.join(','); | ||
61 | + } else { | ||
62 | + labelMap.value = getValue(value); | ||
63 | + } | ||
64 | + break; | ||
65 | + case 'Cascader': | ||
66 | + break; | ||
67 | + case 'DatePicker': | ||
68 | + case 'MapSelect': | ||
69 | + case 'InputNumber': | ||
70 | + case 'Input': | ||
71 | + case 'Input.TextArea': | ||
72 | + case 'UploadFiles': | ||
73 | + labelMap.value = value; | ||
74 | + break; | ||
75 | + case 'DatePicker.RangePicker': | ||
76 | + if (value && value.length > 0) { | ||
77 | + const start = value[0] ? moment(value[0] * 1000).format(format[0]) : ''; | ||
78 | + const end = value[1] ? moment(value[1] * 1000).format(format[1]) : ''; | ||
79 | + labelMap.value = `${start}~${end}`; | ||
80 | + } else { | ||
81 | + labelMap.value = undefined; | ||
82 | + } | ||
83 | + break; | ||
84 | + default: | ||
85 | + labelMap.value = getValue(value); | ||
86 | + } | ||
87 | + return labelMap; | ||
88 | +}; | ||
89 | + | ||
90 | +const _getPopupValuesLabel = (field: any, searchLabels: SearchChooseProps[]) => { | ||
91 | + const { props } = field; | ||
92 | + const { name } = props; | ||
93 | + | ||
94 | + const searchLableMap: any = {}; | ||
95 | + const changeLabel = _getValueLable(field); | ||
96 | + if (searchLabels.length > 0) { | ||
97 | + searchLabels.map((label) => { | ||
98 | + if (name == label.name) { | ||
99 | + if (changeLabel && changeLabel.value) { | ||
100 | + searchLableMap[label.name] = changeLabel; | ||
101 | + } else { | ||
102 | + delete searchLableMap[label.name]; | ||
103 | + } | ||
104 | + } else { | ||
105 | + searchLableMap[label.name] = label; | ||
106 | + } | ||
107 | + }); | ||
108 | + } | ||
109 | + if (changeLabel && changeLabel.value) { | ||
110 | + searchLableMap[name] = changeLabel; | ||
111 | + } | ||
112 | + return searchLableMap; | ||
113 | +}; | ||
114 | + | ||
115 | +const SearchWrap: React.FC<SearchWrapProps> = (props) => { | ||
116 | + const { | ||
117 | + leftSectionComponents = [], | ||
118 | + searchInput, | ||
119 | + popupContent, | ||
120 | + searchChange, | ||
121 | + moreCategories = false, | ||
122 | + ProForm, | ||
123 | + getFormInstance, | ||
124 | + } = props; | ||
125 | + const [isPopupVisible, setPopupVisible] = useState(false); | ||
126 | + const [open, setOpen] = useState(false); | ||
127 | + const [searchLabels, setSearchLabels] = useState<SearchChooseProps[]>([]); | ||
128 | + const [showPopupValue, setShowPopupValue] = useState<boolean>(false); | ||
129 | + const [centerWidth, setCenterWidth] = useState<number>(0); | ||
130 | + const valueChange = useDebounce((value: any) => { | ||
131 | + searchChange && searchChange(value); | ||
132 | + }, 800); | ||
133 | + | ||
134 | + const form = ProForm ? ProForm : useMemo(() => createForm(), []); | ||
135 | + | ||
136 | + useEffect(() => { | ||
137 | + if (searchLabels && searchLabels.length === 0) { | ||
138 | + setShowPopupValue(false); | ||
139 | + } | ||
140 | + }, [searchLabels]); | ||
141 | + | ||
142 | + useEffect(() => { | ||
143 | + form.addEffects('popup_search', () => { | ||
144 | + onFieldValueChange('*', (field) => { | ||
145 | + const { | ||
146 | + address: { entire = '' }, | ||
147 | + } = field; | ||
148 | + if ((entire as string).indexOf('popup') === -1) { | ||
149 | + searchChange && valueChange(form.values); | ||
150 | + } | ||
151 | + if ((entire as string).indexOf('popup') > -1) { | ||
152 | + setSearchLabels((searchLabels) => { | ||
153 | + const labelValues = _getPopupValuesLabel(field, searchLabels); | ||
154 | + | ||
155 | + return Object.values(labelValues); | ||
156 | + }); | ||
157 | + } | ||
158 | + }); | ||
159 | + }); | ||
160 | + }, [ProForm]); | ||
161 | + | ||
162 | + useEffect(() => { | ||
163 | + getFormInstance && getFormInstance(form); | ||
164 | + }, [getFormInstance]); | ||
165 | + | ||
166 | + useEffect(() => { | ||
167 | + const leftWidth = document.querySelector('.left-section')?.clientWidth || 0; | ||
168 | + const rightWidth = document.querySelector('.right-section')?.clientWidth || 0; | ||
169 | + const clientWidth = document.querySelector('.ant-layout-content')?.clientWidth || 0; | ||
170 | + const width = clientWidth - leftWidth - rightWidth - 85; | ||
171 | + setCenterWidth(width); | ||
172 | + }, []); | ||
173 | + | ||
174 | + const togglePopup = () => { | ||
175 | + setPopupVisible(!isPopupVisible); | ||
176 | + setOpen(!open); | ||
177 | + }; | ||
178 | + | ||
179 | + const submit = () => { | ||
180 | + const { values: allValue } = form; | ||
181 | + searchChange && searchChange(allValue); | ||
182 | + togglePopup(); | ||
183 | + setShowPopupValue(true); | ||
184 | + }; | ||
185 | + | ||
186 | + const closeLabel = (label: SearchChooseProps) => { | ||
187 | + const { path } = label; | ||
188 | + const field = form.query(path).take(); | ||
189 | + if (field && field.setValue) { | ||
190 | + field.setValue(undefined); | ||
191 | + } | ||
192 | + const { values: allValue } = form; | ||
193 | + searchChange && searchChange(allValue); | ||
194 | + }; | ||
195 | + | ||
196 | + const handleCancle = () => { | ||
197 | + form.reset('popup.*'); | ||
198 | + const { values: allValue } = form; | ||
199 | + searchChange && searchChange(allValue); | ||
200 | + togglePopup(); | ||
201 | + }; | ||
202 | + | ||
203 | + const text = () => { | ||
204 | + return ( | ||
205 | + <div> | ||
206 | + <VoidField name="popup"> | ||
207 | + {popupContent && | ||
208 | + popupContent.map((item, index) => { | ||
209 | + return <div key={index}>{item}</div>; | ||
210 | + })} | ||
211 | + </VoidField> | ||
212 | + <div className="btn"> | ||
213 | + <Button onClick={handleCancle}>重置</Button> | ||
214 | + <Button style={{ marginLeft: 10 }} type="primary" onClick={submit}> | ||
215 | + 确定 | ||
216 | + </Button> | ||
217 | + </div> | ||
218 | + </div> | ||
219 | + ); | ||
220 | + }; | ||
221 | + | ||
222 | + const labels = () => { | ||
223 | + // 左边显示操作6个搜索条件 | ||
224 | + const len = (leftSectionComponents && leftSectionComponents.length - 1) || 0; | ||
225 | + return ( | ||
226 | + <div style={{ width: centerWidth, overflow: 'hidden' }}> | ||
227 | + {showPopupValue && | ||
228 | + searchLabels && | ||
229 | + searchLabels.slice(0, 6 - len).map((item, index) => { | ||
230 | + return ( | ||
231 | + <Tag key={item.name} closable onClose={() => closeLabel(item)}> | ||
232 | + {item.value} | ||
233 | + </Tag> | ||
234 | + ); | ||
235 | + })} | ||
236 | + </div> | ||
237 | + ); | ||
238 | + }; | ||
239 | + | ||
240 | + return ( | ||
241 | + <FormProvider form={form}> | ||
242 | + <div className="search-component"> | ||
243 | + <Space className="left-section"> | ||
244 | + {leftSectionComponents.slice(0, 6).map((component, index) => ( | ||
245 | + <div key={index} className="left-section-component"> | ||
246 | + {component} | ||
247 | + </div> | ||
248 | + ))} | ||
249 | + {labels()} | ||
250 | + </Space> | ||
251 | + <div className="right-section"> | ||
252 | + <div className="section-search">{searchInput}</div> | ||
253 | + {moreCategories && ( | ||
254 | + <Tooltip | ||
255 | + placement="bottomRight" | ||
256 | + overlayStyle={{ padding: 0 }} | ||
257 | + color="#fff" | ||
258 | + title={text} | ||
259 | + overlayClassName="popups" | ||
260 | + trigger="click" | ||
261 | + zIndex={10} | ||
262 | + open={open} | ||
263 | + getPopupContainer={(triggerNode) => triggerNode.parentNode as any} | ||
264 | + > | ||
265 | + <div | ||
266 | + style={{ cursor: 'pointer' }} | ||
267 | + onClick={() => { | ||
268 | + setOpen(!open); | ||
269 | + }} | ||
270 | + > | ||
271 | + <span className="more">更多筛选</span> | ||
272 | + <img className="img" src={ossAddress['searchMore']}></img> | ||
273 | + </div> | ||
274 | + </Tooltip> | ||
275 | + )} | ||
276 | + </div> | ||
277 | + </div> | ||
278 | + </FormProvider> | ||
279 | + ); | ||
280 | +}; | ||
281 | + | ||
282 | +export default SearchWrap; |
packages/hr/table/index.less
0 → 100644
1 | +.table_components { | ||
2 | + .table { | ||
3 | + :global { | ||
4 | + .ant-table-thead { | ||
5 | + background: #f2f3f5; | ||
6 | + font-size: 14px; | ||
7 | + font-weight: 500; | ||
8 | + color: #1d2129; | ||
9 | + height: 54px; | ||
10 | + | ||
11 | + .ant-table-cell { | ||
12 | + &::before { | ||
13 | + width: 0 !important; | ||
14 | + } | ||
15 | + } | ||
16 | + } | ||
17 | + | ||
18 | + .ant-table-tbody { | ||
19 | + background-color: #fff; | ||
20 | + | ||
21 | + .ant-table-row { | ||
22 | + font-size: 14px; | ||
23 | + font-weight: 400; | ||
24 | + color: #303133; | ||
25 | + } | ||
26 | + } | ||
27 | + } | ||
28 | + } | ||
29 | +} |
packages/hr/table/table.tsx
0 → 100644
1 | +import { Table } from 'antd'; | ||
2 | +import type { TableProps } from 'antd'; | ||
3 | +import React, { useEffect, useState } from 'react'; | ||
4 | +import moment from 'moment'; | ||
5 | + | ||
6 | +import s from './index.less'; | ||
7 | + | ||
8 | +export interface tableProps extends TableProps<Record<string, number>> { | ||
9 | + tableHeader?: React.ReactNode; | ||
10 | +} | ||
11 | + | ||
12 | +export const dealNumber = (val: number) => { | ||
13 | + if (val && val != null) { | ||
14 | + let money = String(val); | ||
15 | + let left = money.split('.')[0], | ||
16 | + right = money.split('.')[1]; | ||
17 | + right = right ? (right.length >= 2 ? '.' + right.substr(0, 2) : '.' + right + '0') : '.00'; | ||
18 | + let temp: any = left | ||
19 | + .split('') | ||
20 | + .reverse() | ||
21 | + .join('') | ||
22 | + .match(/(\d{1,3})/g); // 将字符串顺序反转 :字符串-->数组 --> 顺序反转-->字符串 | ||
23 | + return (Number(money) < 0 ? '-' : '') + temp.join(',').split('').reverse().join('') + right; | ||
24 | + } else if (val === 0) { | ||
25 | + return '0.00'; | ||
26 | + } else { | ||
27 | + return ''; | ||
28 | + } | ||
29 | +}; | ||
30 | + | ||
31 | +const TableWrap: React.FC<tableProps> = (props) => { | ||
32 | + const { tableHeader, columns, scroll } = props; | ||
33 | + const [newScroll, setNewScroll] = useState<any>(0); | ||
34 | + const [tableColumns, setTableColumns] = useState<any>([]); | ||
35 | + | ||
36 | + const sex = { | ||
37 | + '1': '男', | ||
38 | + '2': '女', | ||
39 | + }; | ||
40 | + | ||
41 | + const handleAttr = (item: any) => { | ||
42 | + const data: Record<string, any> = { | ||
43 | + date: (text: any) => moment(text).format(item.formatDate || 'YYYY/MM/DD'), | ||
44 | + unix: (text: any) => moment(text * 1000).format(item.formatDate || 'YYYY/MM/DD'), | ||
45 | + decimal: (text: any) => text.toFixed(2), | ||
46 | + money: (text: any) => dealNumber(text), | ||
47 | + sex: (text: keyof typeof sex) => sex[text] || '', | ||
48 | + }; | ||
49 | + | ||
50 | + if (data[item.attr]) item.render = data[item.attr]; | ||
51 | + }; | ||
52 | + | ||
53 | + useEffect(() => { | ||
54 | + let calculateScroll = 0; | ||
55 | + columns?.map((item: any) => { | ||
56 | + // 处理attr | ||
57 | + if (item.attr) { | ||
58 | + handleAttr(item); | ||
59 | + } | ||
60 | + // 设置ellipsis | ||
61 | + item.ellipsis = true; | ||
62 | + // 计算scroll | ||
63 | + if (item.width) calculateScroll += item.width; | ||
64 | + }); | ||
65 | + | ||
66 | + setTableColumns(columns); | ||
67 | + setNewScroll(calculateScroll); | ||
68 | + }, [columns]); | ||
69 | + | ||
70 | + return ( | ||
71 | + <div className={s.table_components}> | ||
72 | + {tableHeader && <div className={s.table_header}>{tableHeader}</div>} | ||
73 | + <Table | ||
74 | + className={s.table} | ||
75 | + pagination={false} | ||
76 | + rowKey={(row) => row.id as unknown as string} | ||
77 | + scroll={scroll || { x: newScroll }} | ||
78 | + dataSource={props.dataSource || []} | ||
79 | + columns={tableColumns} | ||
80 | + {...props} | ||
81 | + /> | ||
82 | + </div> | ||
83 | + ); | ||
84 | +}; | ||
85 | + | ||
86 | +export default TableWrap; |
packages/hr/tabs/index.tsx
0 → 100644
1 | + | ||
2 | +import { Tabs as MyTabs } from 'antd'; | ||
3 | +import { TabsProps } from 'antd/lib/Tabs'; | ||
4 | +import './tabs.less'; | ||
5 | + | ||
6 | +const Tabs: React.FC<TabsProps> = (props) => { | ||
7 | + const { defaultActiveKey, items = [], ...other } = props; | ||
8 | + const defaultKey = defaultActiveKey || items[0].key; | ||
9 | + | ||
10 | + return ( | ||
11 | + <div className={'tabs_warp'}> | ||
12 | + <MyTabs defaultActiveKey={defaultKey} items={items} {...other} /> | ||
13 | + </div> | ||
14 | + ); | ||
15 | +}; | ||
16 | + | ||
17 | +export default Tabs; |
packages/hr/tabs/tabs.less
0 → 100644
1 | +.tabs_warp { | ||
2 | + .ant-tabs { | ||
3 | + background-color: #fff; | ||
4 | + line-height: 20px; | ||
5 | + font-size: 16px; | ||
6 | + font-weight: 400; | ||
7 | + border-radius: 8px 8px 0 0; | ||
8 | + | ||
9 | + .ant-tabs-nav { | ||
10 | + margin: 0; | ||
11 | + | ||
12 | + &::before { | ||
13 | + border-color: #f2f3f5; | ||
14 | + } | ||
15 | + | ||
16 | + .ant-tabs-nav-wrap { | ||
17 | + padding: 0 32px; | ||
18 | + | ||
19 | + .ant-tabs-tab { | ||
20 | + padding: 31px 0 21px 0; | ||
21 | + | ||
22 | + .ant-tabs-tab-btn { | ||
23 | + font-size: 16px; | ||
24 | + color: #606266; | ||
25 | + | ||
26 | + &::after { | ||
27 | + content: ' '; | ||
28 | + width: 20px; | ||
29 | + position: absolute; | ||
30 | + left: calc(~'50% - 10px'); | ||
31 | + bottom: 1px; | ||
32 | + border-bottom: 2px solid transparent; | ||
33 | + box-shadow: 0px 4px 10px 0px rgba(78, 89, 105, 0.06); | ||
34 | + border-radius: 2px; | ||
35 | + transition: 0.4s; | ||
36 | + transform: scaleX(0); | ||
37 | + } | ||
38 | + } | ||
39 | + } | ||
40 | + | ||
41 | + .ant-tabs-tab-active { | ||
42 | + .ant-tabs-tab-btn { | ||
43 | + font-size: 16px; | ||
44 | + color: #1d2129 !important; | ||
45 | + | ||
46 | + &::after { | ||
47 | + border-color: #1677ff; | ||
48 | + transform: scaleX(1); | ||
49 | + } | ||
50 | + } | ||
51 | + } | ||
52 | + | ||
53 | + .ant-tabs-ink-bar { | ||
54 | + display: none; | ||
55 | + } | ||
56 | + } | ||
57 | + } | ||
58 | + } | ||
59 | +} |
packages/hr/typings.ts
0 → 100644
1 | +import { ReactElement } from 'react'; | ||
2 | + | ||
3 | +export interface FieldProps { | ||
4 | + name: string | [string, string]; | ||
5 | + title?: string | ReactElement; | ||
6 | + validator?: any; | ||
7 | + [props: string]: any; | ||
8 | + decoratorProps?: { | ||
9 | + [props: string]: any; | ||
10 | + }; | ||
11 | + componentProps?: { | ||
12 | + [props: string]: any; | ||
13 | + }; | ||
14 | +} |
packages/hr/upload_file/index.less
0 → 100644
packages/hr/upload_file/index.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | ||
2 | +import { message, Upload, notification, Button } from 'antd'; | ||
3 | +import { uploadToken } from '@/services/upload'; | ||
4 | +import { UploadFile, UploadListType, UploadProps } from 'antd/lib/upload/interface'; | ||
5 | +import { UploadOutlined } from '@ant-design/icons'; | ||
6 | +import cx from 'classnames'; | ||
7 | + | ||
8 | +interface UploadFileProps { | ||
9 | + accept?: string; | ||
10 | + listType?: UploadListType; | ||
11 | + Callback?: (e: any) => void; | ||
12 | + num?: number; | ||
13 | + action?: string; | ||
14 | + name?: string | undefined; | ||
15 | + maxSize?: number; | ||
16 | + onChange?: (val: any) => void; | ||
17 | + value?: any; | ||
18 | + [prop: string]: any; | ||
19 | +} | ||
20 | + | ||
21 | +type Config = Record< | ||
22 | + | 'action' | ||
23 | + | 'OSSAccessKeyId' | ||
24 | + | 'key' | ||
25 | + | 'policy' | ||
26 | + | 'signature' | ||
27 | + | 'callback' | ||
28 | + | 'x:access_token' | ||
29 | + // | 'x-oss-security-token' | ||
30 | + | 'success_action_status', | ||
31 | + string | ||
32 | +>; | ||
33 | + | ||
34 | +// picture-card | ||
35 | +const UploadFiles: React.FC<UploadFileProps> = (props) => { | ||
36 | + const { | ||
37 | + value, | ||
38 | + onChange, | ||
39 | + Callback, | ||
40 | + accept = '*.*', | ||
41 | + maxSize = 52428800, | ||
42 | + children, | ||
43 | + multiple, | ||
44 | + object_type, | ||
45 | + } = props; | ||
46 | + | ||
47 | + const [fileLists, setFileList] = useState<Array<UploadFile>>([]); | ||
48 | + const [config, setConfig] = useState<Config>(); | ||
49 | + //解决Warning: Can‘t perform a React state update on an unmounted component.问题 | ||
50 | + const [isFlag, setIsFlag] = useState(true); | ||
51 | + | ||
52 | + useEffect(() => { | ||
53 | + if (!isFlag) { | ||
54 | + return; | ||
55 | + } | ||
56 | + uploadToken({ | ||
57 | + access_type: 'web_upload', | ||
58 | + object_type: object_type || 'recruit', | ||
59 | + }).then((res) => { | ||
60 | + if (res.errors) { | ||
61 | + notification.open({ | ||
62 | + message: '错误', | ||
63 | + description: res.message, | ||
64 | + duration: 2, | ||
65 | + }); | ||
66 | + } else { | ||
67 | + const data = { | ||
68 | + action: `https://${res.bucket}.${res.domain}/`, | ||
69 | + OSSAccessKeyId: res.access_key_id, | ||
70 | + key: res.object_path + '/' + '${filename}', | ||
71 | + policy: res.policy, | ||
72 | + signature: res.signature, | ||
73 | + callback: res.callback_body, | ||
74 | + 'x:access_token': res.callback_token, | ||
75 | + // 测试环境不能使用 | ||
76 | + // 'x-oss-security-token': res.security_token, | ||
77 | + success_action_status: '200', | ||
78 | + }; | ||
79 | + | ||
80 | + setConfig(data); | ||
81 | + } | ||
82 | + }); | ||
83 | + return () => setIsFlag(false); | ||
84 | + }, []); | ||
85 | + | ||
86 | + const onchange = ({ fileList }: { fileList: any }) => { | ||
87 | + if (fileList[fileList.length - 1].status === 'done') { | ||
88 | + // setFileList([...fileLists, fileList[fileList.length - 1]]); | ||
89 | + | ||
90 | + if (onChange) { | ||
91 | + onChange([...fileLists, fileList[fileList.length - 1]]); | ||
92 | + } | ||
93 | + if (Callback) Callback([...fileLists, fileList[fileList.length - 1]]); | ||
94 | + } | ||
95 | + }; | ||
96 | + | ||
97 | + const onRemove = (file: UploadFile) => { | ||
98 | + const files = value.filter((v: { uid: string }) => v.uid !== file.uid); | ||
99 | + setFileList(files); | ||
100 | + if (onChange) { | ||
101 | + onChange(files); | ||
102 | + } | ||
103 | + if (Callback) Callback([...files]); | ||
104 | + }; | ||
105 | + | ||
106 | + const onPreview = async (file: any) => { | ||
107 | + let src = file.url; | ||
108 | + if (!src) { | ||
109 | + src = await new Promise((resolve) => { | ||
110 | + const reader = new FileReader(); | ||
111 | + | ||
112 | + reader.readAsDataURL(file.originFileObj); | ||
113 | + reader.onload = () => resolve(reader.result); | ||
114 | + }); | ||
115 | + } | ||
116 | + const image = new Image(); | ||
117 | + image.src = src; | ||
118 | + const imgWindow = window.open(src); | ||
119 | + imgWindow?.document.write(image.outerHTML); | ||
120 | + }; | ||
121 | + | ||
122 | + const beforeUpload = (file: UploadFile) => { | ||
123 | + // 如果限制了上传类型 | ||
124 | + if (accept !== '*.*') { | ||
125 | + const { name } = file; | ||
126 | + const accepts = accept.toLowerCase().split(','); | ||
127 | + const names = name.toLowerCase().split('.'); | ||
128 | + const extName = names[names.length - 1]; | ||
129 | + if (!accepts.includes(`.${extName}`)) { | ||
130 | + message.error('不支持的文件类型!'); | ||
131 | + return false; | ||
132 | + } | ||
133 | + } | ||
134 | + // 如果限制了上传大小 | ||
135 | + if (maxSize) { | ||
136 | + const { size = 0 } = file; | ||
137 | + | ||
138 | + if (size > maxSize) { | ||
139 | + message.error('文件太大了!'); | ||
140 | + return false; | ||
141 | + } | ||
142 | + } | ||
143 | + return true; | ||
144 | + }; | ||
145 | + | ||
146 | + const uploadProps: UploadProps = { | ||
147 | + accept: props.accept || '*.*', | ||
148 | + action: config?.action, | ||
149 | + name: 'file', | ||
150 | + data: config, | ||
151 | + showUploadList: false, | ||
152 | + multiple: multiple || false, | ||
153 | + beforeUpload: beforeUpload, | ||
154 | + onPreview: onPreview, | ||
155 | + onChange: onchange, | ||
156 | + onRemove: onRemove, | ||
157 | + }; | ||
158 | + | ||
159 | + return ( | ||
160 | + <div className={cx('global_upload')}> | ||
161 | + <Upload {...uploadProps}> | ||
162 | + {children || <Button icon={<UploadOutlined />}>上传文件</Button>} | ||
163 | + </Upload> | ||
164 | + </div> | ||
165 | + ); | ||
166 | +}; | ||
167 | + | ||
168 | +export default UploadFiles; |
packages/hr/wushuju_page/index.less
0 → 100644
1 | +.wushuju_page_warp { | ||
2 | + background-color: #fff; | ||
3 | + text-align: center; | ||
4 | + padding-top: 127px; | ||
5 | + | ||
6 | + .wushuju_icon { | ||
7 | + width: 156px; | ||
8 | + height: 155px; | ||
9 | + margin-bottom: 10px; | ||
10 | + } | ||
11 | + | ||
12 | + .wushuju_word { | ||
13 | + font-size: 16px; | ||
14 | + font-weight: 400; | ||
15 | + color: #1d2129; | ||
16 | + line-height: 26px; | ||
17 | + padding-bottom: 132px; | ||
18 | + } | ||
19 | +} |
packages/hr/wushuju_page/index.tsx
0 → 100644
1 | + | ||
2 | +import cx from 'classnames'; | ||
3 | +import './index.less'; | ||
4 | +import { ossAddress } from '@/utils/ossAddress'; | ||
5 | + | ||
6 | +const WushujuPage: React.FC = () => { | ||
7 | + return ( | ||
8 | + <div className={cx('wushuju_page_warp')}> | ||
9 | + <img className={cx('wushuju_icon')} src={ossAddress['nodata']} /> | ||
10 | + <div className={cx('wushuju_word')}>暂无数据~</div> | ||
11 | + </div> | ||
12 | + ); | ||
13 | +}; | ||
14 | + | ||
15 | +export default WushujuPage; |
packages/hro/typings.d.ts
0 → 100644
services/upload.ts
0 → 100644
tsconfig.json
0 → 100644
1 | +{ | ||
2 | + "compilerOptions": { | ||
3 | + "target": "ESNext", | ||
4 | + "useDefineForClassFields": true, | ||
5 | + "lib": ["DOM", "DOM.Iterable", "ESNext"], | ||
6 | + "allowJs": false, | ||
7 | + "skipLibCheck": true, | ||
8 | + "esModuleInterop": false, | ||
9 | + "strict": true, | ||
10 | + "forceConsistentCasingInFileNames": true, | ||
11 | + "module": "ESNext", | ||
12 | + "moduleResolution": "Node", | ||
13 | + "resolveJsonModule": true, | ||
14 | + "isolatedModules": true, | ||
15 | + "jsx": "react-jsx", | ||
16 | + "allowSyntheticDefaultImports": true, | ||
17 | + "paths": { | ||
18 | + "@/hr/*": ["./packages/hr/*"], | ||
19 | + "@/hro/*": ["./packages/hro/*"], | ||
20 | + "@/utils/*": ["./utils/*"], | ||
21 | + "@/services/*": ["./services/*"] | ||
22 | + }, | ||
23 | + "declaration": true, // 自动生成声明文件 | ||
24 | + "declarationDir": "./", // 声明文件生成的目录 | ||
25 | + "noEmitOnError": false | ||
26 | + }, | ||
27 | + "include": ["packages", "index.tsx", "typings.d.ts"], | ||
28 | + "exclude": ["node_modules"], | ||
29 | + "references": [{ "path": "./tsconfig.node.json" }] | ||
30 | +} |
tsconfig.node.json
0 → 100644
typings.d.ts
0 → 100644
utils/areaData.ts
0 → 100644
此 diff 太大无法显示。
utils/format.ts
0 → 100644
1 | +import { useCallback, useEffect, useRef } from 'react'; | ||
2 | + | ||
3 | +interface IArea { | ||
4 | + value: string; | ||
5 | + label: string; | ||
6 | + children?: IArea[]; | ||
7 | +} | ||
8 | + | ||
9 | +// 省市区信息平铺 | ||
10 | +export const getAreaData = (data: IArea[]) => { | ||
11 | + let areaData: any = []; | ||
12 | + const eachArea = (items: IArea[]) => { | ||
13 | + items.map((item, i) => { | ||
14 | + areaData.push({ | ||
15 | + value: item.value, | ||
16 | + label: item.label, | ||
17 | + }); | ||
18 | + if (item.children) eachArea(item.children); | ||
19 | + }); | ||
20 | + }; | ||
21 | + eachArea(data); | ||
22 | + return areaData; | ||
23 | +}; | ||
24 | + | ||
25 | +// 根据code数组和地区信息返回数组地区名称 | ||
26 | +export const getAreaNamesByCodes = (codes: string[], areaData: IArea[]) => { | ||
27 | + let names: any = []; | ||
28 | + const eachName = (items: IArea[]) => { | ||
29 | + items.map((item, i) => { | ||
30 | + if (codes.includes(item.value)) { | ||
31 | + names.push(item.label); | ||
32 | + } | ||
33 | + }); | ||
34 | + }; | ||
35 | + eachName(areaData); | ||
36 | + return names; | ||
37 | +}; | ||
38 | + | ||
39 | +export function useDebounce(fn: any, delay: number) { | ||
40 | + const { current } = useRef({ fn, timer: null as unknown as NodeJS.Timeout }); | ||
41 | + useEffect( | ||
42 | + function () { | ||
43 | + current.fn = fn; | ||
44 | + }, | ||
45 | + [current.fn, fn], | ||
46 | + ); | ||
47 | + | ||
48 | + return useCallback( | ||
49 | + function (...args: any[]) { | ||
50 | + if (current.timer) { | ||
51 | + clearTimeout(current.timer as unknown as number); | ||
52 | + } | ||
53 | + current.timer = setTimeout(() => { | ||
54 | + current.fn.call(current.fn, ...args); | ||
55 | + }, delay); | ||
56 | + }, | ||
57 | + [current.fn, current.timer, delay], | ||
58 | + ); | ||
59 | +} |
utils/ossAddress.ts
0 → 100644
1 | +export const ossAddress = { | ||
2 | + defaultPhone: 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/resumephotosmall.png', | ||
3 | + woman03: | ||
4 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E5%A5%B301%402x.png', | ||
5 | + woman02: | ||
6 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E5%A5%B302%402x.png', | ||
7 | + woman01: | ||
8 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E5%A5%B303%402x.png', | ||
9 | + man05: | ||
10 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E7%94%B705%402x.png', | ||
11 | + man04: | ||
12 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E7%94%B704%402x.png', | ||
13 | + man03: | ||
14 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E7%94%B703%402x.png', | ||
15 | + man02: | ||
16 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E7%94%B702%402x.png', | ||
17 | + man01: | ||
18 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%A4%B4%E5%83%8F%E7%94%B701%402x.png', | ||
19 | + xianju: | ||
20 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E7%8E%B0%E5%B1%85%E4%BD%8F%E5%9C%B0%402x.png', | ||
21 | + huji: 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E6%88%B7%E5%8F%A3%E5%9C%B0%402x.png', | ||
22 | + searchMore: 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/more.png', | ||
23 | + add_cabdidate_button: 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/add_candate.png', | ||
24 | + add_cabdidate_icon: | ||
25 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E5%9F%BA%E6%9C%AC%E4%BF%A1%E6%81%AF.png', | ||
26 | + recruit_jobseekers_detail_photo: | ||
27 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/resumephoto.png', | ||
28 | + recruit_jobseekers_detail_phone: | ||
29 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/phone.png', | ||
30 | + recruit_jobseekers_detail_email: | ||
31 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/email.png', | ||
32 | + recruit_jobseekers_detail_idcard: | ||
33 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/idcard.png', | ||
34 | + recruit_jobseekers_detail_fileicon: | ||
35 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/fileicon.png', | ||
36 | + nodata: | ||
37 | + 'https://hropublic.oss-cn-beijing.aliyuncs.com/hr-micro/%E6%9A%82%E6%97%A0%E6%95%B0%E6%8D%AE%402x.png', | ||
38 | +}; |
vite.config.ts
0 → 100644
1 | +import { defineConfig } from 'vite'; | ||
2 | +import react from '@vitejs/plugin-react'; | ||
3 | +import { resolve } from 'path'; | ||
4 | + | ||
5 | +// https://vitejs.dev/config/ | ||
6 | +export default defineConfig({ | ||
7 | + plugins: [react()], | ||
8 | + server: { | ||
9 | + host: '127.0.0.1', | ||
10 | + port: 10012, | ||
11 | + strictPort: true, | ||
12 | + open: true, | ||
13 | + hmr: true, | ||
14 | + }, | ||
15 | + esbuild: { | ||
16 | + jsxFactory: 'h', | ||
17 | + jsxFragment: 'Fragment', | ||
18 | + }, | ||
19 | + resolve: { | ||
20 | + alias: { | ||
21 | + '@/hr': resolve(__dirname, 'packages/hr'), | ||
22 | + '@/hro': resolve(__dirname, 'packages/hro'), | ||
23 | + '@/utils': resolve(__dirname, 'utils'), | ||
24 | + '@/services': resolve(__dirname, 'services'), | ||
25 | + }, | ||
26 | + }, | ||
27 | +}); |
请
注册
或
登录
后发表评论