提交 239ca653a88fddbac8fb45adb2d5eacc3a083b6a

作者 juvenile
1 个父辈 fe2637e3

first commit

正在显示 79 个修改的文件 包含 3040 行增加1 行删除
  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 +}
  1 +node_modules
  2 +yarn.lock
  3 +yarn-error.log
  4 +yarn-debug.log*
  5 +npm-debug.log*
  6 +npm-error.log*
  7 +packages-lock.json
  8 +.DS_Store
  9 +.vscode
  10 +*.gzip
  11 +*.zip
  12 +*.tar.gz
  13 +*.tgz
  14 +*.map
  15 +package-lock.*
  16 +pnpm-lock.yaml
  17 +dist
  1 +{
  2 + "src/**/*.js": ["eslint --fix", "pnpm test"],
  3 + "src/**/*.css": ["stylelint --fix"],
  4 + "src/**/*.{js,css,json,md}": ["prettier --write", "git add"]
  5 +}
  1 +{
  2 + "singleQuote": true,
  3 + "jsxSingleQuote": false,
  4 + "trailingComma": "all",
  5 + "printWidth": 100,
  6 + "proseWrap": "never",
  7 + "overrides": [
  8 + {
  9 + "files": ".prettierrc",
  10 + "options": {
  11 + "parser": "json"
  12 + }
  13 + }
  14 + ]
  15 +}
  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 +};
  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>
  1 +import { createRoot } from 'react-dom/client';
  2 +import App from '@/hr/App';
  3 +// Render your React component instead
  4 +const root = createRoot(document.getElementById('app') as HTMLElement);
  5 +root.render(<App />);
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 "main": "index.js", 5 "main": "index.js",
6 "scripts": { 6 "scripts": {
7 "dev": "vite", 7 "dev": "vite",
8 - "build": "vite build", 8 + "build": "vite build && tsc --declaration --emitDeclarationOnly --noEmitOnError false",
9 "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 9 "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
10 "lint-staged": "lint-staged", 10 "lint-staged": "lint-staged",
11 "test": "echo \"Error: no test specified\" && exit 1" 11 "test": "echo \"Error: no test specified\" && exit 1"
  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;
  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 +}
  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;
  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 +}
  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;
  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;
  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 +}
  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 +}
  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;
  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 +}
  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;
  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 +}
  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;
  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 +}
  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;
  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;
  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 +}
  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;
  1 +.bread_warp {
  2 + margin-bottom: 20px;
  3 +
  4 + .ant-breadcrumb li:last-child {
  5 + color: #1d2119;
  6 + }
  7 +}
  1 +import React from 'react';
  2 +import { ItemType } from 'antd/lib/breadcrumb/Breadcrumb';
  3 +import './bread.less';
  4 +interface P {
  5 + separator?: string;
  6 + items: ItemType[];
  7 +}
  8 +declare const Breadcrumb: React.FC<P>;
  9 +export default Breadcrumb;
  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;
  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;
  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;
  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;
  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;
  1 +/// <reference types="react" />
  2 +import { ImageProps } from 'antd';
  3 +interface Props extends ImageProps {
  4 + imgValues?: string[];
  5 + show_image?: boolean;
  6 + setShowImage?: (value: any) => void;
  7 +}
  8 +declare const Image: React.FC<Props>;
  9 +export default Image;
  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;
  1 +import './index.less';
  2 +declare global {
  3 + interface Window {
  4 + _AMapSecurityConfig: any;
  5 + AMapUI: any;
  6 + AMap: any;
  7 + }
  8 +}
  9 +declare const MapSelect: (props: any) => import("react/jsx-runtime").JSX.Element;
  10 +export default MapSelect;
  1 +.map {
  2 + min-width: 400px;
  3 + min-height: 300px;
  4 +}
  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;
  1 +import { ModalFuncProps } from 'antd';
  2 +declare const showPropsConfirm: (props: ModalFuncProps) => void;
  3 +export default showPropsConfirm;
  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;
  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;
  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 +}
  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;
  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 +}
  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;
  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;
  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 +}
  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 +}
  1 +.global_upload {
  2 + margin-bottom: 10px;
  3 +
  4 + .ant-upload {
  5 + .ant-btn-default {
  6 + border-color: #ebeef5;
  7 + }
  8 + }
  9 +}
  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;
  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 +}
  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;
  1 +import request from 'umi-request';
  2 +
  3 +// 获取osstoken
  4 +export const uploadToken = (data: any) => {
  5 + return request('/file_api/filemeta/inits', {
  6 + method: 'POST',
  7 + data,
  8 + });
  9 +};
  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 +}
  1 +{
  2 + "compilerOptions": {
  3 + "composite": true,
  4 + "module": "esnext",
  5 + "moduleResolution": "node"
  6 + },
  7 + "include": ["vite.config.ts"]
  8 +}
  1 +declare module '*.less' {
  2 + const content: { [className: string]: string };
  3 + export default content;
  4 +}
此 diff 太大无法显示。
  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 +}
  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 +};
  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 +});
注册登录 后发表评论