正在显示
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 | 5 | "main": "index.js", |
6 | 6 | "scripts": { |
7 | 7 | "dev": "vite", |
8 | - "build": "vite build", | |
8 | + "build": "vite build && tsc --declaration --emitDeclarationOnly --noEmitOnError false", | |
9 | 9 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", |
10 | 10 | "lint-staged": "lint-staged", |
11 | 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 | +}); | ... | ... |
请
注册
或
登录
后发表评论