diff --git a/.env b/.env new file mode 100644 index 0000000..d7cf348 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +PORT=8888 +COMPRESS=none \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b2b843d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +/node_modules +/.env.local +/.umirc.local.ts +/config/config.local.ts +/src/.umi +/src/.umi-production +/src/.umi-test +/dist + +.idea +.vscode + +yarn.lock +package-lock.json + +.DS_Store +**/.DS_Store +.DS_Store? diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..082c745 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://registry.npmmirror.com +package-lock=false diff --git a/.umirc.ts b/.umirc.ts new file mode 100644 index 0000000..513ad5a --- /dev/null +++ b/.umirc.ts @@ -0,0 +1,50 @@ +import defaultSettings from './config/defaultSettings' +import proxy from './config/proxy' +import routes from './config/routes' +import { defineConfig } from '@umijs/max' + +const { REACT_APP_ENV } = process.env + +export default defineConfig({ + title: defaultSettings.title, + theme: { + 'primary-color': defaultSettings.primaryColor + }, + model: {}, + antd: { + }, + request: {}, + initialState: { + loading: '@/components/Loading' + }, + dva: {}, + // layout: {}, + mfsu: { strategy: 'normal' }, + locale: { + default: 'zh-CN', + antd: true, + baseNavigator: false + }, + fastRefresh: true, + history: { + type: 'browser' + }, + hash: false, + // publicPath: "./", //本地静态化 for android + publicPath: "/", //部署到服务端需要 publicPath: "/",或者 publicPath: "/xxxxx/", + manifest: { + basePath: "/", //本地静态化 for android 部署到服务端需要 publicPath: "/",或者 publicPath: "/xxxxx/", + }, + routes: routes, + proxy: proxy[REACT_APP_ENV || 'dev'], + codeSplitting: { + jsStrategy: 'granularChunks' + }, + chainWebpack(config, { webpack }) { + config.plugin('code-inspector-plugin').use(require('code-inspector-plugin').codeInspectorPlugin, [ + { + bundler: 'webpack', + }, + ]); + }, +}) diff --git a/config/defaultSettings.js b/config/defaultSettings.js new file mode 100644 index 0000000..2e1f29a --- /dev/null +++ b/config/defaultSettings.js @@ -0,0 +1,8 @@ +export default { + primaryColor: '#2969ff', + menu: { + locale: true, + }, + title: '智能巡检系统', + iconfontUrl: '//at.alicdn.com/t/font_2163129_p3ldyoksz3s.js' +} diff --git a/config/proxy.js b/config/proxy.js new file mode 100644 index 0000000..bddbab7 --- /dev/null +++ b/config/proxy.js @@ -0,0 +1,18 @@ +/** + * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 + * The agent cannot take effect in the production environment + * so there is no configuration of the production environment + * For details, please see + * https://pro.ant.design/docs/deploy + */ +export default { + dev: { + '/platform_system/': { + // target: 'http://192.168.7.5:9091', + target: 'http://127.0.0.1:9091', + changeOrigin: true, + pathRewrite: { '^/platform_system': '/platform_system' }, + }, + + }, +} diff --git a/config/routes.js b/config/routes.js new file mode 100644 index 0000000..0614436 --- /dev/null +++ b/config/routes.js @@ -0,0 +1,438 @@ +export default [ + { + path: '/', + redirect: '/topnavbar00/home', + }, + { + name: 'login', + path: '/login', + component: './login/Login' + }, + { + name: 'topnavbar', + icon: 'table', + path: '/topnavbar00', + component: './topnavbar/TopNavBar', + routes: [ + { + path: '/topnavbar00/home', + name: 'home', + component: './home/HomeList' + }, + { + path: '/topnavbar00/business', + name: 'business', + component: './nav_system_content/SystemContentList', + routes: [ + // 安全管理基础信息 + { + path: '/topnavbar00/business/basicinformation', + name: 'basicinformation', + component: './business_basicinformation/BasicInformation', + }, + // 隐患排查 + { + path: '/topnavbar00/business/hiddentrouble', + name: 'hiddentrouble', + component: './business_hiddentrouble/HiddenTrouble', + }, + ], + }, + ], + }, + + // 异常页 + { + name: 'exception', + icon: 'warning', + path: '/exception00', + hideInMenu: true, + routes: [ + // exception + { + path: '/exception00/403', + name: 'not-permission', + component: './exception/403' + }, + { + path: '/exception00/404', + name: 'not-find', + component: './exception/404' + }, + { + path: '/exception00/500', + name: 'server-error', + component: './exception/500' + }, + { + path: '/exception00/1403', + name: 'not-license-permission', + hideLayout: true, + component: './exception/1403' + } + ] + }, + + + + { + // icon, + hideInMenu: true, + routes: [ + { + // 网站通用图标 + routes: [ + { icon: 'accountBook' }, + { icon: 'aim' }, + { icon: 'alert' }, + { icon: 'apartment' }, + { icon: 'api' }, + { icon: 'appstoreAdd' }, + { icon: 'appstore' }, + { icon: 'audio' }, + { icon: 'audioMuted' }, + { icon: 'audit' }, + { icon: 'bank' }, + { icon: 'barcode' }, + { icon: 'bars' }, + { icon: 'bell' }, + { icon: 'block' }, + { icon: 'book' }, + { icon: 'border' }, + { icon: 'borderlessTable' }, + { icon: 'branches' }, + { icon: 'bug' }, + { icon: 'build' }, + { icon: 'bulb' }, + { icon: 'calculator' }, + { icon: 'calendar' }, + { icon: 'camera' }, + { icon: 'car' }, + { icon: 'carryOut' }, + { icon: 'ciCircle' }, + { icon: 'ci' }, + { icon: 'clear' }, + { icon: 'cloudDownload' }, + { icon: 'cloud' }, + { icon: 'cloudServer' }, + { icon: 'cloudSync' }, + { icon: 'cloudUpload' }, + { icon: 'cluster' }, + { icon: 'code' }, + { icon: 'coffee' }, + { icon: 'comment' }, + { icon: 'compass' }, + { icon: 'compress' }, + { icon: 'consoleSql' }, + { icon: 'contacts' }, + { icon: 'container' }, + { icon: 'control' }, + { icon: 'copyright' }, + { icon: 'creditCard' }, + { icon: 'crown' }, + { icon: 'customerService' }, + { icon: 'dashboard' }, + { icon: 'database' }, + { icon: 'deleteColumn' }, + { icon: 'deleteRow' }, + { icon: 'deliveredProcedure' }, + { icon: 'deploymentUnit' }, + { icon: 'desktop' }, + { icon: 'dingtalk' }, + { icon: 'disconnect' }, + { icon: 'dislike' }, + { icon: 'dollarCircle' }, + { icon: 'dollar' }, + { icon: 'download' }, + { icon: 'ellipsis' }, + { icon: 'environment' }, + { icon: 'euroCircle' }, + { icon: 'euro' }, + { icon: 'exception' }, + { icon: 'expandAlt' }, + { icon: 'expand' }, + { icon: 'experiment' }, + { icon: 'export' }, + { icon: 'eye' }, + { icon: 'fieldBinary' }, + { icon: 'fieldNumber' }, + { icon: 'fieldString' }, + { icon: 'fieldTime' }, + { icon: 'fileAdd' }, + { icon: 'fileDone' }, + { icon: 'fileExcel' }, + { icon: 'fileExclamation' }, + { icon: 'file' }, + { icon: 'fileGif' }, + { icon: 'fileImage' }, + { icon: 'fileJpg' }, + { icon: 'fileMarkdown' }, + { icon: 'filePdf' }, + { icon: 'filePpt' }, + { icon: 'fileProtect' }, + { icon: 'fileSearch' }, + { icon: 'fileSync' }, + { icon: 'fileText' }, + { icon: 'fileUnknown' }, + { icon: 'fileWord' }, + { icon: 'fileZip' }, + { icon: 'filter' }, + { icon: 'fire' }, + { icon: 'flag' }, + { icon: 'folderAdd' }, + { icon: 'folder' }, + { icon: 'folderView' }, + { icon: 'fork' }, + { icon: 'formatPainter' }, + { icon: 'frown' }, + { icon: 'function' }, + { icon: 'fundProjectionScreen' }, + { icon: 'fundView' }, + { icon: 'funnelPlot' }, + { icon: 'gateway' }, + { icon: 'gif' }, + { icon: 'gift' }, + { icon: 'global' }, + { icon: 'gold' }, + { icon: 'group' }, + { icon: 'hdd' }, + { icon: 'heart' }, + { icon: 'history' }, + { icon: 'home' }, + { icon: 'hourglass' }, + { icon: 'idcard' }, + { icon: 'import' }, + { icon: 'inbox' }, + { icon: 'insertRowAbove' }, + { icon: 'insertRowBelow' }, + { icon: 'insertRowLeft' }, + { icon: 'insertRowRight' }, + { icon: 'insurance' }, + { icon: 'interaction' }, + { icon: 'key' }, + { icon: 'laptop' }, + { icon: 'layout' }, + { icon: 'like' }, + { icon: 'line' }, + { icon: 'link' }, + { icon: 'loading3Quarters' }, + { icon: 'loading' }, + { icon: 'lock' }, + { icon: 'macCommand' }, + { icon: 'mail' }, + { icon: 'man' }, + { icon: 'medicineBox' }, + { icon: 'meh' }, + { icon: 'menu' }, + { icon: 'mergeCells' }, + { icon: 'message' }, + { icon: 'mobile' }, + { icon: 'moneyCollect' }, + { icon: 'monitor' }, + { icon: 'more' }, + { icon: 'nodeCollapse' }, + { icon: 'nodeExpand' }, + { icon: 'nodeIndex' }, + { icon: 'notification' }, + { icon: 'number' }, + { icon: 'oneToOne' }, + { icon: 'partition' }, + { icon: 'payCircle' }, + { icon: 'percentage' }, + { icon: 'phone' }, + { icon: 'picture' }, + { icon: 'playSquare' }, + { icon: 'poundCircle' }, + { icon: 'pound' }, + { icon: 'poweroff' }, + { icon: 'printer' }, + { icon: 'profile' }, + { icon: 'project' }, + { icon: 'propertySafety' }, + { icon: 'pullRequest' }, + { icon: 'pushpin' }, + { icon: 'qrcode' }, + { icon: 'read' }, + { icon: 'reconciliation' }, + { icon: 'redEnvelope' }, + { icon: 'reload' }, + { icon: 'rest' }, + { icon: 'robot' }, + { icon: 'rocket' }, + { icon: 'rotateLeft' }, + { icon: 'rotateRight' }, + { icon: 'safetyCertificate' }, + { icon: 'safety' }, + { icon: 'save' }, + { icon: 'scan' }, + { icon: 'schedule' }, + { icon: 'search' }, + { icon: 'securityScan' }, + { icon: 'select' }, + { icon: 'send' }, + { icon: 'setting' }, + { icon: 'shake' }, + { icon: 'shareAlt' }, + { icon: 'shop' }, + { icon: 'shoppingCart' }, + { icon: 'shopping' }, + { icon: 'sisternode' }, + { icon: 'skin' }, + { icon: 'smile' }, + { icon: 'solution' }, + { icon: 'sound' }, + { icon: 'splitCells' }, + { icon: 'star' }, + { icon: 'subnode' }, + { icon: 'switcher' }, + { icon: 'sync' }, + { icon: 'table' }, + { icon: 'tablet' }, + { icon: 'tag' }, + { icon: 'tags' }, + { icon: 'team' }, + { icon: 'thunderbolt' }, + { icon: 'toTop' }, + { icon: 'tool' }, + { icon: 'trademarkCircle' }, + { icon: 'trademark' }, + { icon: 'transaction' }, + { icon: 'translation' }, + { icon: 'trophy' }, + { icon: 'ungroup' }, + { icon: 'unlock' }, + { icon: 'upload' }, + { icon: 'usb' }, + { icon: 'userAdd' }, + { icon: 'userDelete' }, + { icon: 'user' }, + { icon: 'userSwitch' }, + { icon: 'usergroupAdd' }, + { icon: 'usergroupDelete' }, + { icon: 'verified' }, + { icon: 'videoCameraAdd' }, + { icon: 'videoCamera' }, + { icon: 'wallet' }, + { icon: 'whatsApp' }, + { icon: 'wifi' }, + { icon: 'woman' }, + ] + }, + { + // 编辑类图标 + routes: [ + { icon: 'edit' }, + { icon: 'form' }, + { icon: 'copy' }, + { icon: 'scissor' }, + { icon: 'delete' }, + { icon: 'snippets' }, + { icon: 'diff' }, + { icon: 'highlight' }, + { icon: 'alignCenter' }, + { icon: 'alignLeft' }, + { icon: 'alignRight' }, + { icon: 'bgColors' }, + { icon: 'bold' }, + { icon: 'italic' }, + { icon: 'underline' }, + { icon: 'strikethrough' }, + { icon: 'redo' }, + { icon: 'undo' }, + { icon: 'zoomIn' }, + { icon: 'zoomOut' }, + { icon: 'fontColors' }, + { icon: 'fontSize' }, + { icon: 'lineHeight' }, + { icon: 'dash' }, + { icon: 'smallDash' }, + { icon: 'sortAscending' }, + { icon: 'sortDescending' }, + { icon: 'drag' }, + { icon: 'orderedList' }, + { icon: 'unorderedList' }, + { icon: 'radiusSetting' }, + { icon: 'columnWidth' }, + { icon: 'columnHeight' }, + ] + }, + { + // 数据类图标 + routes: [ + { icon: 'areaChart' }, + { icon: 'pieChart' }, + { icon: 'barChart' }, + { icon: 'dotChart' }, + { icon: 'lineChart' }, + { icon: 'radarChart' }, + { icon: 'heatMap' }, + { icon: 'fall' }, + { icon: 'rise' }, + { icon: 'stock' }, + { icon: 'boxPlot' }, + { icon: 'fund' }, + { icon: 'sliders' }, + ] + }, + { + // 品牌和标识 + routes: [ + { icon: 'android' }, + { icon: 'apple' }, + { icon: 'windows' }, + { icon: 'ie' }, + { icon: 'chrome' }, + { icon: 'github' }, + { icon: 'aliwangwang' }, + { icon: 'dingding' }, + { icon: 'weiboSquare' }, + { icon: 'weiboCircle' }, + { icon: 'taobaoCircle' }, + { icon: 'html5' }, + { icon: 'weibo' }, + { icon: 'twitter' }, + { icon: 'wechat' }, + { icon: 'youtube' }, + { icon: 'alipayCircle' }, + { icon: 'taobao' }, + { icon: 'skype' }, + { icon: 'qq' }, + { icon: 'mediumWorkmark' }, + { icon: 'gitlab' }, + { icon: 'medium' }, + { icon: 'linkedin' }, + { icon: 'googlePlus' }, + { icon: 'dropbox' }, + { icon: 'facebook' }, + { icon: 'codepen' }, + { icon: 'codeSandbox' }, + { icon: 'amazon' }, + { icon: 'google' }, + { icon: 'codepenCircle' }, + { icon: 'alipay' }, + { icon: 'antDesign' }, + { icon: 'antCloud' }, + { icon: 'aliyun' }, + { icon: 'zhihu' }, + { icon: 'slack' }, + { icon: 'slackSquare' }, + { icon: 'behance' }, + { icon: 'behanceSquare' }, + { icon: 'dribbble' }, + { icon: 'dribbbleSquare' }, + { icon: 'instagram' }, + { icon: 'yuque' }, + { icon: 'alibaba' }, + { icon: 'yahoo' }, + { icon: 'reddit' }, + { icon: 'sketch' }, + ] + } + + ], + }, + { + component: './404', + }, + +] diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..388467f --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@@/*": ["./src/.umi/*"], + "umi": ["./node_module/umi"], + "@umijs/max": ["./node_module/umi"], + "@umijs/max/typings": ["./src/.umi/typings"], + "ahooks": ["./node_module/ahooks"], + "@ant-design/icons": ["./node_module/@ant-design/icons"], + "dayjs": ["./node_module/dayjs"] + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..52c1208 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "flx-lms-fe", + "version": "1.0.0", + "private": true, + "author": "", + "scripts": { + "dev": "cross-env REACT_APP_ENV=dev max dev", + "build": "cross-env REACT_APP_ENV=prod max build", + "postinstall": "max setup", + "setup": "max setup", + "start": "cross-env REACT_APP_ENV=dev max dev", + "start:no-mock": "cross-env MOCK=none REACT_APP_ENV=dev max dev" + }, + "dependencies": { + "@ant-design/icons": "^4.8.0", + "@umijs/max": "^4.0.70", + "antd": "5.6.0", + "braft-editor": "^2.3.9", + "braft-finder": "^0.0.21", + "braft-utils": "^3.0.2", + "code-inspector-plugin": "^1.2.8", + "echarts": "5.4.0", + "echarts-for-react": "^3.0.2", + "moment": "^2.29.1", + "nvm": "^0.0.4", + "qs": "^6.11.0", + "react": "^18.2.0", + "react-contexify": "^5.0.0", + "react-dom": "^18.2.0", + "react-split-pane": "^0.1.92" + }, + "devDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "code-inspector-plugin": "^1.2.8", + "cross-env": "^7.0.3", + "typescript": "^4.1.2" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..c7a04ac Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon_copy.ico b/public/favicon_copy.ico new file mode 100644 index 0000000..5c16b73 Binary files /dev/null and b/public/favicon_copy.ico differ diff --git a/public/img/error.jpeg b/public/img/error.jpeg new file mode 100644 index 0000000..f8b9a2e Binary files /dev/null and b/public/img/error.jpeg differ diff --git a/public/img/risk-bg-1.png b/public/img/risk-bg-1.png new file mode 100644 index 0000000..e69de29 diff --git a/public/img/risk-bg-2.png b/public/img/risk-bg-2.png new file mode 100644 index 0000000..e69de29 diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 0000000..83a528f --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,337 @@ +// @ts-nocheck +import React from 'react' +import { history, RequestConfig, type AxiosResponse, matchPath } from '@umijs/max' +import { notification } from 'antd' +import NotificationTemplate from '@/components/NotificationTemplate' +import { getTokenAuthority, setUserInfo } from '@/utils/globalCommon' +import errorCode from '@/utils/errorCode' +import { queryCurrent } from '@/services/user' +import defaultSettings from '../config/defaultSettings' +import { getAllForProMenuByUserId } from '@/services/system/api_promenu' +import { getAllForProResourceActionByUserId } from '@/services/system/api_proresourceaction' +import * as allIcons from '@ant-design/icons' +import 'dayjs/locale/zh-cn' +import dayjs from 'dayjs' +import weekday from 'dayjs/plugin/weekday' +import localeData from 'dayjs/plugin/localeData' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import relativeTime from 'dayjs/plugin/relativeTime' + +dayjs.locale('zh-cn') +dayjs.extend(weekday) +dayjs.extend(localeData) +dayjs.extend(customParseFormat) +dayjs.extend(relativeTime) + +const loginPath = '/login' +const { REACT_APP_ENV } = process.env +let staticMapList = ['/', '/login', '/register', '/exception00', '/exception00/403', '/exception00/404', '/exception00/500', '/exception00/1403'] +let tempMapList = [] + +//约定 src/app.tsx 为运行时配置 +export async function getInitialState (type) { + tempMapList = [] + // 如果是登录页面,不执行 + if (history.location.pathname !== loginPath || type === 'refresh') { + try { + history.replace({ + pathname: "/topnavbar00/business" + }) + + // if (!localStorage.getItem('antd-token-authority')) { + // history.replace({ + // pathname: loginPath + // }) + + // localStorage.clear() + // throw new Error() + // } + + // let response = await queryCurrent() + + // const { datarecord: { user: currentUser, roles } } = response + // localStorage.setItem('antd-token-userid', currentUser.user_id ?? '') + // localStorage.setItem('antd-token-username', currentUser.user_name ?? '') + // localStorage.setItem('antd-token-poweruser', currentUser.poweruser ?? '') + // localStorage.setItem('antd-pro-authority', roles ? JSON.stringify(roles) : '') + // localStorage.setItem('antd-token-user', currentUser ? JSON.stringify(currentUser) : '') + + // setUserInfo() + + // let menuData = await getAllForProMenuByUserId({ + // user_name: currentUser.user_name, + // parentid: '000000000000', + // systemid: '000000000000' + // }) + + // let resourceData = await getAllForProResourceActionByUserId({ + // systemid: '000000000000' + // }) + // localStorage.setItem('antd-pro-button', resourceData?.datarecord?.button ? JSON.stringify(resourceData?.datarecord?.button) : '') + + // const toHump = (name) => name.replace(/-(\w)/g, (all, letter) => letter.toUpperCase()) + + // let tempList = [] + // let tempMap = [] + // const formatter = (data, list) => { + // data?.map(item => { + // tempMap.push((item?.redirect === '' ? null : item?.redirect) ?? item?.key) + // // if (item?.hideInMenu) return + // if (item?.alias === 'ywmk') { + // formatter(item?.children ?? [], tempList) + // return + // } + + // let tempObj = { + // key: (item?.redirect === '' ? null : item?.redirect) ?? item?.key, + // label: item?.label, + // hideInMenu: item?.hideInMenu, + // children: [] + // } + + // if (item?.children || item?.routes) formatter(item?.children?.length > 0 ? item?.children : (item?.routes?.length > 0 ? item?.routes : []), tempObj.children) + + // tempObj.children.length === 0 && delete tempObj.children + + // if (item?.icon) { + // const { icon } = item + // const v4IconName = toHump(icon.replace(icon[0], icon[0].toUpperCase())) + // const NewIcon = allIcons[icon] || allIcons[''.concat(v4IconName, 'Outlined')] + // if (NewIcon) { + // try { + // tempObj.icon = React.createElement(NewIcon) + // } catch (error) {} + // } + // } + + // list.push(tempObj) + // }) + // } + + // formatter(menuData.list ?? [], tempList) + // menuData.list = tempList + // tempMapList = tempMap + // window.dynamicRoute = tempList + + return { + // currentUser, + settings: defaultSettings, + // menu: menuData.list, + // menuMap: tempMap + } + } catch (error) { + notification.error({ + message: '网络异常', + description: '用户信息获取失败!' + }) + + // history.replace({ + // pathname: loginPath + // }) + } + } + + return { + currentUser: null, + menu: null, + settings: defaultSettings + } +} + +export function onRouteChange({ location }) { + const locationName = location.pathname + const menuMap = [...staticMapList, ...tempMapList] + let foundFlag = menuMap?.some(item => !!matchPath(item, locationName)) + //本地开发注释掉下面这一行 + // !foundFlag && (window.location.href = '/exception00/404') +} + +/** + * 在控制台打印出对应的错误信息,方便定位问题 + * @param error + */ +const printErrorInfo = (error: {}) => { + const { code, message, status, timestamp, request = {} } = error || {} + const { url, options } = request + const { method, headers, params, data } = options + const paramsKeys = Object.keys(params || {}) + const tempParams = paramsKeys && paramsKeys.length ? params : data // 接口的参数有可能在 params 中也有可能在 data 里 + + const errorTagStyle = [ + 'color: #ff4d4f', + 'background: #fff2f0', + 'padding: 2px 6px', + 'font-size: 12px', + 'border-radius: 2px', + 'border: 1px solid #ff4d4f' + ].join('') + + console.log('%c接口报错信息', errorTagStyle) + console.log(` + ------------- error-start ------------- + 时间:${timestamp || new Date().toString()} + HTTP Status:${status || 200} + 接口路径:${url} + 请求类型:${method} + headers:${JSON.stringify(headers)} + 参数:${JSON.stringify(tempParams || {})} + code:${code} + message:${message} + ------------- error-end --------------- + `) +} + +export const dva = { + config: { + onError(e: { preventDefault: () => void }) { + e.preventDefault() + } + } +} + +/** + * 统一异常处理程序 + */ +const errorHandler = (error: any) => { + if (error && Object.keys(error).length > 0) { + REACT_APP_ENV === 'dev' && printErrorInfo(error) + const { status, success, message: msg } = error + + if (status === 203 || status === 401) { + /*let duration = 4 + let interval = setInterval(function(){ + if(duration === 0) { + // 跳转到登录页 记录是从哪个页面跳转到登录页的,登录后直接跳转到对应的页面 + const redirect = window.location.pathname === '/' || window.location.pathname === '/topnavbar/home' ? {} : { redirect: window.location.href } + history.replace({ + pathname: loginPath, + search: JSON.stringify(redirect) + }) + + // 跳到登录页时需要将存储在本地的信息全部清除掉 + localStorage.clear() + clearInterval(interval) + } else { + duration-- + notification.error({ + key: 'notification_login', + message: '未登录或登录已过期,请重新登录!', + description: `${duration === 0 ? '即': duration + ' 秒之后'}将跳转到登录页面` + }) + } + }, 1000)*/ + + } else if (status <= 504 && status >= 500) { + history.push('/exception/500') //500 + }/* else if (status === 403) { + history.push('/exception/403') //403 + } */else if (status >= 404 && status < 422) { + history.push('/exception/404') //404 + } else if (status === 1403) { + history.replace({ + pathname: '/exception/1403' + }) //未授权 + } else { + // 获取错误信息 + let errorText = errorCode[status] || msg || errorCode['default'] + + if (errorText == 'Network Error') { + errorText = '后端接口连接异常' + } else if (errorText.includes('timeout')) { + errorText = '系统接口请求超时' + } else if (errorText.includes('Request failed with status code')) { + errorText = '系统接口请求异常' + } + + notification.error({ + message: `请求错误 ${errorText || ''}`, + description: + }) + } + } else { + notification.config({ maxCount: 1 }) + notification.error({ + description: '您的网络发生异常,无法连接服务器', + message: '网络异常' + }) + } + + // reject出去之后使用时才可通过.catch对异常情况进行处理 + // return Promise.reject(error) +} + +/** + * 设置 headers + */ +const getHeaders = () => { + // 统一的headers + return { + Authorization: getTokenAuthority() + } +} + +/** + * request 请求 + */ +export const request: RequestConfig = { + timeout: 20000, + errorConfig: { + errorHandler: errorHandler, // 默认错误处理 + // errorThrower: printErrorInfo // 默认错误抛出 + }, + adaptor: (resData: any) => { + return { + ...resData, + success: true, + errorMessage: resData.message + } + }, + credentials: 'include', // 默认请求是否带上cookie + headers: getHeaders(), + requestInterceptors: [ + (url: any, options: any) => { + let headers = getTokenAuthority() ? getHeaders() : {} + return { + url: url, + options: { ...options, headers: headers } + } + } + ], + responseInterceptors: [ + async (response: AxiosResponse) => { + const responseClone = response || {} + if(responseClone.status === 200) { + // 二进制数据则直接返回 + if(response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') { + return response + } + + const data = await responseClone?.data || {} + let { success, message: msg, status } = data + + if (!success) { + // msg = msg?.split('!')[0] || msg?.match(/[\u4e00-\u9fa5|\uff01|\uff0c|\u3002]/gi).join(') + if(status) { + // 注意:需要reject出去才会在请求不成功或返回错误的status时调用errorHandler + // data.message = msg + return Promise.reject(data) + } else { + // 只保留汉字 !,。 + // message.error(msg) + notification.error({ + message: `${msg?.split('!')[0] || '请求错误'}: `, + description: + }) + } + } + } else { + return Promise.reject({ + status: responseClone.status, + statusText: responseClone.statusText + }) + } + + return response + } + ] +} diff --git a/src/assets/basic_information/add.png b/src/assets/basic_information/add.png new file mode 100644 index 0000000..2f6c06d Binary files /dev/null and b/src/assets/basic_information/add.png differ diff --git a/src/assets/basic_information/certificate-expire.png b/src/assets/basic_information/certificate-expire.png new file mode 100644 index 0000000..78364b4 Binary files /dev/null and b/src/assets/basic_information/certificate-expire.png differ diff --git a/src/assets/basic_information/contract-expire.png b/src/assets/basic_information/contract-expire.png new file mode 100644 index 0000000..c4ca403 Binary files /dev/null and b/src/assets/basic_information/contract-expire.png differ diff --git a/src/assets/basic_information/contract-overdue.png b/src/assets/basic_information/contract-overdue.png new file mode 100644 index 0000000..9038f88 Binary files /dev/null and b/src/assets/basic_information/contract-overdue.png differ diff --git a/src/assets/basic_information/dqtx-area-bg.png b/src/assets/basic_information/dqtx-area-bg.png new file mode 100644 index 0000000..d9c2264 Binary files /dev/null and b/src/assets/basic_information/dqtx-area-bg.png differ diff --git a/src/assets/basic_information/file-icon.png b/src/assets/basic_information/file-icon.png new file mode 100644 index 0000000..247a2dc Binary files /dev/null and b/src/assets/basic_information/file-icon.png differ diff --git a/src/assets/basic_information/func-area-bg.png b/src/assets/basic_information/func-area-bg.png new file mode 100644 index 0000000..7412db1 Binary files /dev/null and b/src/assets/basic_information/func-area-bg.png differ diff --git a/src/assets/basic_information/organ-area-bg.png b/src/assets/basic_information/organ-area-bg.png new file mode 100644 index 0000000..f6ec3e5 Binary files /dev/null and b/src/assets/basic_information/organ-area-bg.png differ diff --git a/src/assets/basic_information/risk-area-bg.png b/src/assets/basic_information/risk-area-bg.png new file mode 100644 index 0000000..9572230 Binary files /dev/null and b/src/assets/basic_information/risk-area-bg.png differ diff --git a/src/assets/basic_information/risk-shield.png b/src/assets/basic_information/risk-shield.png new file mode 100644 index 0000000..70f463b Binary files /dev/null and b/src/assets/basic_information/risk-shield.png differ diff --git a/src/assets/basic_information/risk-tip.png b/src/assets/basic_information/risk-tip.png new file mode 100644 index 0000000..a71cfa3 Binary files /dev/null and b/src/assets/basic_information/risk-tip.png differ diff --git a/src/assets/basic_information/ryzz-area-bg.png b/src/assets/basic_information/ryzz-area-bg.png new file mode 100644 index 0000000..76a9d42 Binary files /dev/null and b/src/assets/basic_information/ryzz-area-bg.png differ diff --git a/src/assets/basic_information/user.png b/src/assets/basic_information/user.png new file mode 100644 index 0000000..2c94be3 Binary files /dev/null and b/src/assets/basic_information/user.png differ diff --git a/src/assets/basic_information/warn-area-bg.png b/src/assets/basic_information/warn-area-bg.png new file mode 100644 index 0000000..4b2ed2a Binary files /dev/null and b/src/assets/basic_information/warn-area-bg.png differ diff --git a/src/assets/dashboard/bg.png b/src/assets/dashboard/bg.png new file mode 100644 index 0000000..1303c00 Binary files /dev/null and b/src/assets/dashboard/bg.png differ diff --git a/src/assets/dashboard/databg.png b/src/assets/dashboard/databg.png new file mode 100644 index 0000000..cede054 Binary files /dev/null and b/src/assets/dashboard/databg.png differ diff --git a/src/assets/dashboard/jt.png b/src/assets/dashboard/jt.png new file mode 100644 index 0000000..1eb5cb0 Binary files /dev/null and b/src/assets/dashboard/jt.png differ diff --git a/src/assets/dashboard/lbx.png b/src/assets/dashboard/lbx.png new file mode 100644 index 0000000..cefb215 Binary files /dev/null and b/src/assets/dashboard/lbx.png differ diff --git a/src/assets/dashboard/left_header.png b/src/assets/dashboard/left_header.png new file mode 100644 index 0000000..4854edb Binary files /dev/null and b/src/assets/dashboard/left_header.png differ diff --git a/src/assets/dashboard/map.png b/src/assets/dashboard/map.png new file mode 100644 index 0000000..2138288 Binary files /dev/null and b/src/assets/dashboard/map.png differ diff --git a/src/assets/dashboard/right_header.png b/src/assets/dashboard/right_header.png new file mode 100644 index 0000000..240f30c Binary files /dev/null and b/src/assets/dashboard/right_header.png differ diff --git a/src/assets/img/book.svg b/src/assets/img/book.svg new file mode 100644 index 0000000..d1aef3b --- /dev/null +++ b/src/assets/img/book.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/danger.svg b/src/assets/img/danger.svg new file mode 100644 index 0000000..e87d4a6 --- /dev/null +++ b/src/assets/img/danger.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/danger1.svg b/src/assets/img/danger1.svg new file mode 100644 index 0000000..9be66a6 --- /dev/null +++ b/src/assets/img/danger1.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/img/fireHydrant.svg b/src/assets/img/fireHydrant.svg new file mode 100644 index 0000000..a15c04a --- /dev/null +++ b/src/assets/img/fireHydrant.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/fireHydrant1.svg b/src/assets/img/fireHydrant1.svg new file mode 100644 index 0000000..0c7b497 --- /dev/null +++ b/src/assets/img/fireHydrant1.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/img/gzjl.png b/src/assets/img/gzjl.png new file mode 100644 index 0000000..ae7532e Binary files /dev/null and b/src/assets/img/gzjl.png differ diff --git a/src/assets/img/jbxx.png b/src/assets/img/jbxx.png new file mode 100644 index 0000000..843813a Binary files /dev/null and b/src/assets/img/jbxx.png differ diff --git a/src/assets/img/jyjl.png b/src/assets/img/jyjl.png new file mode 100644 index 0000000..e1c2ec7 Binary files /dev/null and b/src/assets/img/jyjl.png differ diff --git a/src/assets/img/license.svg b/src/assets/img/license.svg new file mode 100644 index 0000000..a6996e5 --- /dev/null +++ b/src/assets/img/license.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/memuTitle.png b/src/assets/img/memuTitle.png new file mode 100644 index 0000000..e42bc98 Binary files /dev/null and b/src/assets/img/memuTitle.png differ diff --git a/src/assets/img/memuTitle1.png b/src/assets/img/memuTitle1.png new file mode 100644 index 0000000..8dc02f1 Binary files /dev/null and b/src/assets/img/memuTitle1.png differ diff --git a/src/assets/img/people.svg b/src/assets/img/people.svg new file mode 100644 index 0000000..7e68013 --- /dev/null +++ b/src/assets/img/people.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/img/risk.svg b/src/assets/img/risk.svg new file mode 100644 index 0000000..d51b483 --- /dev/null +++ b/src/assets/img/risk.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/stepFour.png b/src/assets/img/stepFour.png new file mode 100644 index 0000000..44f06a7 Binary files /dev/null and b/src/assets/img/stepFour.png differ diff --git a/src/assets/img/stepOne.png b/src/assets/img/stepOne.png new file mode 100644 index 0000000..cc775cb Binary files /dev/null and b/src/assets/img/stepOne.png differ diff --git a/src/assets/img/stepThree.png b/src/assets/img/stepThree.png new file mode 100644 index 0000000..678d947 Binary files /dev/null and b/src/assets/img/stepThree.png differ diff --git a/src/assets/img/stepTwo.png b/src/assets/img/stepTwo.png new file mode 100644 index 0000000..d5de828 Binary files /dev/null and b/src/assets/img/stepTwo.png differ diff --git a/src/assets/img/trouble.svg b/src/assets/img/trouble.svg new file mode 100644 index 0000000..94d7763 --- /dev/null +++ b/src/assets/img/trouble.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/user_avatar.png b/src/assets/img/user_avatar.png new file mode 100644 index 0000000..b92d13f Binary files /dev/null and b/src/assets/img/user_avatar.png differ diff --git a/src/assets/img/下拉箭头.svg b/src/assets/img/下拉箭头.svg new file mode 100644 index 0000000..85c6468 --- /dev/null +++ b/src/assets/img/下拉箭头.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/img/智能管控平台-1.svg b/src/assets/img/智能管控平台-1.svg new file mode 100644 index 0000000..06f318c --- /dev/null +++ b/src/assets/img/智能管控平台-1.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/img/智能管控平台.svg b/src/assets/img/智能管控平台.svg new file mode 100644 index 0000000..e46fcc8 --- /dev/null +++ b/src/assets/img/智能管控平台.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/login/login.png b/src/assets/login/login.png new file mode 100644 index 0000000..2d970e2 Binary files /dev/null and b/src/assets/login/login.png differ diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..cf5e77d Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/logo_copy.png b/src/assets/logo_copy.png new file mode 100644 index 0000000..ebe1331 Binary files /dev/null and b/src/assets/logo_copy.png differ diff --git a/src/assets/register.png b/src/assets/register.png new file mode 100644 index 0000000..ad52eb1 Binary files /dev/null and b/src/assets/register.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/alarm0.png b/src/assets/safe_majorHazard/online_monitoring/alarm0.png new file mode 100644 index 0000000..f7ed384 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/alarm0.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/alarm1.png b/src/assets/safe_majorHazard/online_monitoring/alarm1.png new file mode 100644 index 0000000..70ee597 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/alarm1.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/alarm2.png b/src/assets/safe_majorHazard/online_monitoring/alarm2.png new file mode 100644 index 0000000..b825d9c Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/alarm2.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/alarm3.png b/src/assets/safe_majorHazard/online_monitoring/alarm3.png new file mode 100644 index 0000000..a4c93eb Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/alarm3.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/backTopRight.png b/src/assets/safe_majorHazard/online_monitoring/backTopRight.png new file mode 100644 index 0000000..de7c15b Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/backTopRight.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/delete.png b/src/assets/safe_majorHazard/online_monitoring/delete.png new file mode 100644 index 0000000..24aa367 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/delete.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/export.png b/src/assets/safe_majorHazard/online_monitoring/export.png new file mode 100644 index 0000000..b2beef1 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/export.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/img1.png b/src/assets/safe_majorHazard/online_monitoring/img1.png new file mode 100644 index 0000000..deac813 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/img1.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/img2.png b/src/assets/safe_majorHazard/online_monitoring/img2.png new file mode 100644 index 0000000..b4515eb Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/img2.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/img3.png b/src/assets/safe_majorHazard/online_monitoring/img3.png new file mode 100644 index 0000000..564b82c Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/img3.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/map.png b/src/assets/safe_majorHazard/online_monitoring/map.png new file mode 100644 index 0000000..6b6931b Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/map.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/risk1.png b/src/assets/safe_majorHazard/online_monitoring/risk1.png new file mode 100644 index 0000000..d1058f1 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/risk1.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/risk2.png b/src/assets/safe_majorHazard/online_monitoring/risk2.png new file mode 100644 index 0000000..089cbed Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/risk2.png differ diff --git a/src/assets/safe_majorHazard/online_monitoring/risk3.png b/src/assets/safe_majorHazard/online_monitoring/risk3.png new file mode 100644 index 0000000..24f6038 Binary files /dev/null and b/src/assets/safe_majorHazard/online_monitoring/risk3.png differ diff --git a/src/assets/yay.jpg b/src/assets/yay.jpg new file mode 100644 index 0000000..e72bd8f Binary files /dev/null and b/src/assets/yay.jpg differ diff --git a/src/components/A01UserTable/index.js b/src/components/A01UserTable/index.js new file mode 100644 index 0000000..eddbc7d --- /dev/null +++ b/src/components/A01UserTable/index.js @@ -0,0 +1,162 @@ +import React, { PureComponent } from 'react'; +import { connect } from 'umi'; +import { Modal, Tooltip, Input, Button } from 'antd'; + +import './index.less'; +import style from "@/global.less"; + +import StandardTable from '@/components/StandardTable'; +import { AX } from '@/utils/pr_new_datadictionary'; +import { initTreeSelect } from '@/utils/globalCommon' + +@connect(({ baseinfodata, loading }) => ({ + baseinfodata, + loading: loading.models.baseinfodata, +})) + +class A01UserTable extends PureComponent { + + + constructor(props) { + super(props); + this.state = { + visible: false, + selectedRows: [], + selectedRowKeys: [], + formValues: '', + }; + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if (undefined != nextProps.visible) { + this.setState({ visible: nextProps.visible }); + if(!nextProps.visible) { + this.setState({ selectedRows: [], formValues: '', selectedRowKeys: [] }); + } + } + // if (undefined != nextProps.value) { + // this.setState({ selectedRowKeys: nextProps.value. }); + // } + } + + columns = [ + { + title: '姓名', + dataIndex: 'za0101', + key: 'za0101', + width: 80, + fixed: 'left', + }, + { + title: '性别', + dataIndex: 'aa0107', + key: 'aa0107', + render: val => { + const list = AX || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + ] + + componentDidMount() { + const { dispatch } = this.props; + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + }); + } + + handleModalVisible() { + const { getChildrenMsg } = this.props; + const { selectedRows } = this.state; + if (getChildrenMsg && selectedRows[0]) { + getChildrenMsg(false, selectedRows[0]); + } else { + getChildrenMsg(false); + } + } + + handleSelectRows = (selectedRowKeys, selectedRows) => { + this.setState({ + selectedRowKeys: selectedRowKeys, + selectedRows: selectedRows, + }); + }; + + handleStandardTableChange = (pagination, filtersArg, sorter) => { + + const { dispatch } = this.props; + const { formValues } = this.state; + + const params = { + currentPage: pagination.current, + pageSize: pagination.pageSize, + id: formValues || undefined, + }; + + if (sorter.field) { + params.sorter = `${sorter.field}_${sorter.order}`; + } + + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: params, + }); + }; + + handleChangeSearch(e) { + this.setState({ formValues: e.target.value }) + } + + handleSearch() { + const { formValues } = this.state; + const params = { id: formValues } + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: params, + }); + } + render() { + const { + baseinfodata: { data }, + loading, + } = this.props; + const { visible, selectedRows, selectedRowKeys } = this.state; + return ( +
+ this.handleModalVisible(false)} + onCancel={() => this.handleModalVisible(false)} + > +
+ this.handleChangeSearch(e)} placeholder="" style={{ width: '100%' }} /> + +
+ +
+
+ ); + } +} + +export default A01UserTable; diff --git a/src/components/A01UserTable/index.less b/src/components/A01UserTable/index.less new file mode 100644 index 0000000..1867140 --- /dev/null +++ b/src/components/A01UserTable/index.less @@ -0,0 +1,3 @@ +.a01-user-table { + +} \ No newline at end of file diff --git a/src/components/AsyncTree/index.js b/src/components/AsyncTree/index.js new file mode 100644 index 0000000..6f745f1 --- /dev/null +++ b/src/components/AsyncTree/index.js @@ -0,0 +1,446 @@ +import React, {PureComponent} from 'react'; +import {Input, message, Tree, Modal} from 'antd'; +import {DeleteOutlined, EditOutlined, FileOutlined, PlusCircleOutlined, ExclamationCircleOutlined} from '@ant-design/icons'; +import {contextMenu, Item, Menu} from 'react-contexify'; +import 'react-contexify/dist/ReactContexify.min.css'; +import {getNodeByKeyAndCallbackProcess} from '@/components/_utils/algorithmTools'; +import {connect} from '@umijs/max'; + +const {Search} = Input; + +@connect(({globaldata, loading}) => ({ + globaldata, + loading: loading.models.globaldata, +})) +class AsyncTree extends PureComponent { + state = { + expandedKeys: ['000000000000'], + autoExpandParent: true, + selectedKeys: [], + treeData: [], + selectedTreeByRightClick: "", + searchValue: '', + dataList: [], + display: "block", + }; + + constructor(props) { + super(props); + this.state.treeData = [ + {title: props.rootNodeName, id: '000000000000', key: '000000000000', value: '000000000000', levelcode: "000000000000"}, + ] + } + + componentDidMount() { + const {dispatch, getTreeMethod, reQueryParent} = this.props; + if(!reQueryParent) { + return + } + dispatch({ + type: 'globaldata/' + getTreeMethod, + payload: { + parentid: "#" + }, + callback: (res) => { + console.log(res) + if (res.success == true && res?.list.length > 0) { + const temList = res.list?.map((item) => { + return { + key: item.id, + ...item + } + }) + this.setState({ + treeData: temList + }) + } + } + }); + } + + //展开收起 + onExpand = expandedKeys => { + this.setState({ + expandedKeys, + autoExpandParent: false, + }); + }; + + // 选中的树节点 + onSelect = (selectedKeys, {node}) => { + const {checkedTreeChild} = this.props; + this.setState({selectedKeys}); + if(checkedTreeChild) { + checkedTreeChild(node); + } + }; + + addMenuTreeNode = () => { + const {handleModalVisible} = this.props; + handleModalVisible(true, this.state.selectedTreeByRightClick, this.reLoadCurrentTreeNode); + }; + + updateMenuTreeNode = () => { + const {handleUpdateModalVisible} = this.props; + handleUpdateModalVisible(true, this.state.selectedTreeByRightClick, this.reLoadCurrentTreeNodeUpdate); + }; + + viewMenuTreeNode = () => { + const {handleViewModalVisible} = this.props; + handleViewModalVisible(true, this.state.selectedTreeByRightClick); + }; + + deleteMenuTreeNode = () => { + const {handleDeleteRecord} = this.props; + Modal.confirm({ + title: '你确定要删除吗?', + icon: , + content: '删除该节点后其子节点也会删除!这并不会删除该节点及其子节点的的关联数据', + okText: '确认', + cancelText: '取消', + onOk: () => { + handleDeleteRecord(this.state.selectedTreeByRightClick, this.reLoadCurrentTreeNodeDelete) + }, + }); + + }; + + // 响应右键点击 + rightClickTreeNode = ({event, node}) => { + event.preventDefault(); + contextMenu.show({ + id: 'menu_id', + event: event, + props: { + foo: 'bar' + } + }); + + if (node.key != '000000000000') { + this.setState({ + selectedTreeByRightClick: node, + display: "block", + }); + } else { + this.setState({ + selectedTreeByRightClick: node, + display: "none", + }); + } + } + + // 右键菜单 + MyAwesomeMenu = () => ( + + + + 添加 + + + + 修改 + + + + 查看 + + + + 删除 + + + ); + + // 组装树数据 + updateTreeData(list, key, children) { + return list.map(node => { + if (node.key === key) { + return { + ...node, + children, + }; + } else if (node.children) { + return { + ...node, + children: this.updateTreeData(node.children, key, children), + }; + } + return node; + }); + } + + /** + * 异步加载树数据 + * @param key + * @param children + * @returns {Promise} + */ + onLoadData = ({key, children}) => { + const {dispatch, getTreeMethod} = this.props; + + const params = { + parentid: key + }; + + return new Promise((resolve) => { + // if (children) { + // resolve(); + // return; + // } + dispatch({ + type: 'globaldata/' + getTreeMethod, + payload: params, + callback: (res) => { + if (res.success == true) { + const temList = res.list?.map((item) => { + return { + key: item.id, + ...item + } + }) + this.setState({ + treeData: this.updateTreeData(this.state.treeData, key, temList) + }); + } + resolve(); + + } + }); + }); + }; + + + reLoadCurrentTreeNode = (treeNode, dropKey) => { + const data = [...this.state.treeData]; + treeNode.key = treeNode.id; + getNodeByKeyAndCallbackProcess(data, dropKey, (item) => { + item.children = item.children || []; + // where to insert 示例添加到尾部,可以是随意位置 + item.children.push(treeNode); + }); + + this.setState({ + treeData: data, + }); + + }; + + + reLoadCurrentTreeNodeUpdate = (treeNode, currentTreeNodeKey, parentTreeNodeKey) => { + const data = [...this.state.treeData]; + + getNodeByKeyAndCallbackProcess(data, parentTreeNodeKey, (item) => { + item.children = item.children || {}; + item.children = item.children.map((item, key) => item.key == currentTreeNodeKey ? {...item, ...treeNode} : item) + // item.children.push(treeNode); + }); + this.setState({ + treeData: data, + }); + } + + + reLoadCurrentTreeNodeDelete = (currentTreeNodeKey) => { + + const data = [...this.state.treeData]; + getNodeByKeyAndCallbackProcess(data, currentTreeNodeKey, (item, index, arr) => { + arr.splice(index, 1); + }); + + this.setState({ + treeData: data, + }); + } + + // 搜索事件 + onChange = e => { + const {treeData, dataList} = this.state; + const {value} = e.target; + const expandedKeys = dataList.map(item => { + if (item.title.indexOf(value) > -1) { + return this.getParentKey(item.title, treeData); + } + return null; + }).filter((item, i, self) => item && self.indexOf(item) === i); + this.setState({ + expandedKeys, + searchValue: value, + autoExpandParent: true, + }); + }; + + getParentKey = (title, tree) => { + let parentKey; + for (let i = 0; i < tree.length; i++) { + const node = tree[i]; + if (node.children) { + if (node.children.some(item => item.title === title)) { + parentKey = node.key; + } else if (this.getParentKey(title, node.children)) { + parentKey = this.getParentKey(title, node.children); + } + } + } + return parentKey; + }; + + generateList = data => { + + if (undefined == data) { + return false; + } + + for (let i = 0; i < data.length; i++) { + const node = data[i]; + const {key, title} = node; + this.state.dataList.push({key, title: title}); + if (node.children) { + this.generateList(node.children); + } + } + }; + + onDrop = async info => { + const dropKey = info.node.key; + const dragKey = info.dragNode.key; + const dropPos = info.node.pos.split('-'); + const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); + + const source_parent_id = info.dragNode.parentid; + const source_levelcode = info.dragNode.levelcode; + let new_levelcode = ""; + let dragKey_parent_id = ""; + let sequences = ""; + let sequencesArray = []; + + const data = [...this.state.treeData]; + + // Find dragObject + let dragObj; + getNodeByKeyAndCallbackProcess(data, dragKey, (item, index, arr) => { + arr.splice(index, 1); + dragObj = item; + }); + + if (!info.dropToGap) { + let ar; + // Drop on the content 移动到未展开的节点里 + getNodeByKeyAndCallbackProcess(data, dropKey, (item, index) => { + new_levelcode = item.levelcode + "/" + dragKey; + dragKey_parent_id = item.key; + ar = item.children || []; + + dragObj.levelcode = new_levelcode; + dragObj.parentid = dragKey_parent_id; + item.children = item.children || []; + sequences = item.children.length; + // where to insert 示例添加到尾部,可以是随意位置 push + item.children.unshift(dragObj); + }); + ar.forEach((item, index) => { + let arrs = { + id: item.key, + sequences: index + } + sequencesArray.push(arrs); + }) + } else if ( + (info.node.props.children || []).length > 0 && // Has children + info.node.props.expanded && // Is expanded + dropPosition === 1 // On the bottom gap + ) { + getNodeByKeyAndCallbackProcess(data, dropKey, (item) => { + new_levelcode = item.levelcode + "/" + dragKey; + dragKey_parent_id = item.key + + dragObj.levelcode = new_levelcode; + dragObj.parentid = dragKey_parent_id; + item.children = item.children || []; + item.children.unshift(dragObj); + }); + } else { + let ar; + let i; + getNodeByKeyAndCallbackProcess(data, dropKey, (item, index, arr) => { + new_levelcode = item.levelcode.replace(item.key, "") + dragKey; + dragKey_parent_id = item.parentid + + dragObj.levelcode = new_levelcode; + dragObj.parentid = dragKey_parent_id; + ar = arr; + i = index; + }); + if (dropPosition === -1) { + sequences = i; + ar.splice(i, 0, dragObj); + } else { + sequences = i + 1; + ar.splice(i + 1, 0, dragObj); + } + ar.forEach((item, index, arr) => { + if(sequences <= index) { + let arrs = { + id: item.key, + sequences: index + } + sequencesArray.push(arrs); + } + }) + } + + const {dispatch, updateTreeNodeByDragUrl} = this.props; + + await dispatch({ + type: 'globaldata/' + updateTreeNodeByDragUrl, + payload: { + source_id: dragKey, + source_parentid: source_parent_id, + source_levelcode: source_levelcode, + target_id: dragKey_parent_id, + levelcode: new_levelcode, + sequences: sequences, + sequencesArray: sequencesArray + }, + callback: async (res) => { + if(res.success == true) { + message.success('修改成功'); + await this.setState({ + treeData: data, + }); + } + + } + }); + + } + + render() { + const { height, modify } = this.props; + const {autoExpandParent, expandedKeys, selectedKeys, treeData} = this.state; + this.generateList(treeData); + + return ( +
+ + + + {modify ? this.MyAwesomeMenu() : null} +
+ ); + } +} + +export default AsyncTree; diff --git a/src/components/AsyncTree/index.less b/src/components/AsyncTree/index.less new file mode 100644 index 0000000..9ad8a5f --- /dev/null +++ b/src/components/AsyncTree/index.less @@ -0,0 +1,2 @@ +@import '~antd/es/style/themes/default.less'; + diff --git a/src/components/AvatarList/index.less b/src/components/AvatarList/index.less new file mode 100644 index 0000000..4162f20 --- /dev/null +++ b/src/components/AvatarList/index.less @@ -0,0 +1,57 @@ +@avatar-size-base: 32px; +@avatar-size-lg: 40px; +@avatar-size-sm: 24px; +@font-size-base: 14px; +@border-color-base: hsv(0, 0, 85%); + +.avatarList { + display: inline-block; + + ul { + display: inline-block; + margin-left: 8px; + font-size: 0; + } +} + +.avatarItem { + display: inline-block; + width: @avatar-size-base; + height: @avatar-size-base; + margin-left: -8px; + font-size: @font-size-base; + + :global { + .ant-avatar { + border: 1px solid @border-color-base; + } + } +} + +.avatarItemLarge { + width: @avatar-size-lg; + height: @avatar-size-lg; +} + +.avatarItemSmall { + width: @avatar-size-sm; + height: @avatar-size-sm; +} + +.avatarItemMini { + width: 20px; + height: 20px; + + :global { + .ant-avatar { + width: 20px; + height: 20px; + line-height: 20px; + + .ant-avatar-string { + font-size: 12px; + line-height: 18px; + } + } + } +} diff --git a/src/components/AvatarList/index.tsx b/src/components/AvatarList/index.tsx new file mode 100644 index 0000000..c7093f7 --- /dev/null +++ b/src/components/AvatarList/index.tsx @@ -0,0 +1,73 @@ +import { Avatar, Tooltip } from 'antd' +import React from 'react' +import classNames from 'classnames' +import styles from './index.less' + +export declare type SizeType = number | 'small' | 'default' | 'large' + +export interface AvatarItemProps { + tips: React.ReactNode + src: string + size?: SizeType + style?: React.CSSProperties + onClick?: () => void +} + +export interface AvatarListProps { + Item?: React.ReactElement + size?: SizeType + maxLength?: number + excessItemsStyle?: React.CSSProperties + style?: React.CSSProperties + children: React.ReactElement | React.ReactElement[] +} + +const avatarSizeToClassName = (size?: SizeType | 'mini') => + classNames(styles.avatarItem, { + [styles.avatarItemLarge]: size === 'large', + [styles.avatarItemSmall]: size === 'small', + [styles.avatarItemMini]: size === 'mini' + }) + +const Item: React.FC = ({ src, size, tips, onClick = () => {} }) => { + const cls = avatarSizeToClassName(size) + + return ( +
  • + {tips ? ( + + + + ) : ( + + )} +
  • + ) +} + +const AvatarList: React.FC & { Item: typeof Item } = ({children, size, maxLength = 5, excessItemsStyle, ...other}) => { + const numOfChildren = React.Children.count(children) + const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength + const childrenArray = React.Children.toArray(children) as React.ReactElement[] + const childrenWithProps = childrenArray.slice(0, numToShow).map((child) => React.cloneElement(child, { size })) + + if (numToShow < numOfChildren) { + const cls = avatarSizeToClassName(size) + + childrenWithProps.push( +
  • + {`+${numOfChildren - maxLength}`} +
  • + ) + } + + return ( +
    +
      {childrenWithProps}
    +
    + ) +} + +AvatarList.Item = Item + +export default AvatarList diff --git a/src/components/BusinessBase/Aa01Table.js b/src/components/BusinessBase/Aa01Table.js new file mode 100644 index 0000000..cc6dd63 --- /dev/null +++ b/src/components/BusinessBase/Aa01Table.js @@ -0,0 +1,1169 @@ +import { PureComponent, Fragment } from 'react'; +import { DownOutlined, SearchOutlined, EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { history, connect } from 'umi'; + +import { + Card, + Button, + Dropdown, + Menu, + message, + DatePicker, + Divider, + Popconfirm, + Tooltip +} from 'antd'; + +import { initTreeSelect } from '@/utils/globalCommon' +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import Aa01CreateForm from './form/Aa01CreateForm'; //新增表单 +import Aa01UpdateForm from './form/Aa01UpdateForm'; //修改表单 +import Aa01ViewForm from './form/Aa01ViewForm'; //查看表单 +import Aa01RenderSimpleForm from './form/Aa01RenderSimpleForm'; //简单查询表单 +import Aa01RenderAdvancedForm from './form/Aa01RenderAdvancedForm'; //高级查询表单 +import {formatDate,formartDateObject} from '@/utils/formatUtils' + + +import StandardTable from '@/components/StandardTable'; + +import style from '@/global.less'; +import styles from './index.less'; + +const { RangePicker } = DatePicker; + + +/* eslint react/no-multi-comp:0 */ +@connect(({ baseinfodata, loading }) => ({ + baseinfodata, + loading: loading.models.baseinfodata, +})) +class Aa01Table extends PureComponent { + + state = { + modalVisible: false, + updateModalVisible: false, + viewModalVisible: false, + expandForm: false, + selectedRows: [], + formValues: {}, + updateFormValues: {}, + viewFormValues: {}, + }; + + columns = [ + // { + // title: 'ID主键', + // dataIndex: 'id', + // key: 'id', + // }, + { + title: '工号', + dataIndex: 'gh', + key: 'gh', + width: 100, + fixed: 'left' + }, + { + title: '姓名', + dataIndex: 'za0101', + key: 'za0101', + width: 80, + fixed: 'left', + }, + { + title: '性别', + dataIndex: 'aa0107', + key: 'aa0107', + render: val => { + const list = AX || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '手机', + dataIndex: 'ak010b', + key: 'ak010b', + }, + { + title: '电话', + dataIndex: 'ak010i', + key: 'ak010i', + }, + + { + title: '年龄', + dataIndex: 'ae0141', + key: 'ae0141', + }, + { + title: '助记码', + dataIndex: 'zjm', + key: 'zjm', + }, + { + title: '公司', + dataIndex: 'gs', + key: 'gs', + }, + { + title: '部门', + dataIndex: 'v_aa011c', + key: 'v_aa011c', + }, + { + title: '职位', + dataIndex: 'zw', + key: 'zw', + }, + { + title: '职称', + dataIndex: 'zc', + key: 'zc', + }, + { + title: '险种', + dataIndex: 'xz', + key: 'xz', + }, + { + title: '身份证', + dataIndex: 'aa0177', + key: 'aa0177', + }, + { + title: '考勤卡号', + dataIndex: 'kqkh', + key: 'kqkh', + }, + { + title: '学历', + dataIndex: 'xl', + key: 'xl', + }, + { + title: '婚姻状况', + dataIndex: 'aa0127', + key: 'aa0127', + render: val => { + const list = BG || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '员工密码', + dataIndex: 'ygmm', + key: 'ygmm', + }, + { + title: '确认密码', + dataIndex: 'qrmm', + key: 'qrmm', + }, + { + title: '学位', + dataIndex: 'xw', + key: 'xw', + }, + { + title: '籍贯', + dataIndex: 'aa0114', + key: 'aa0114', + render: val => { + const list = AB || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '出生日期', + dataIndex: 'aa0111', + key: 'aa0111', + }, + { + title: '民族', + dataIndex: 'aa0121', + key: 'aa0121', + render: val => { + const list = AE || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '政治面貌', + dataIndex: 'aa2205', + key: 'aa2205', + render: val => { + const list = AT || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '类别', + dataIndex: 'lb', + key: 'lb', + }, + { + title: '毕业学校', + dataIndex: 'byxx', + key: 'byxx', + }, + { + title: '所学专业', + dataIndex: 'sxzy', + key: 'sxzy', + }, + { + title: '毕业日期', + dataIndex: 'byrq', + key: 'byrq', + }, + { + title: '基本工资', + dataIndex: 'jbgz', + key: 'jbgz', + }, + { + title: '家庭住址', + dataIndex: 'ak010n', + key: 'ak010n', + }, + { + title: '户口地址', + dataIndex: 'ak010m', + key: 'ak010m', + }, + { + title: '微信账号', + dataIndex: 'wxzh', + key: 'wxzh', + }, + { + title: '档案地址', + dataIndex: 'dadz', + key: 'dadz', + }, + { + title: '联系人', + dataIndex: 'lxr', + key: 'lxr', + }, + { + title: '联系人电话', + dataIndex: 'lxrdh', + key: 'lxrdh', + }, + { + title: '紧急联系人', + dataIndex: 'ae010m', + key: 'ae010m', + }, + { + title: '紧急联系人电话', + dataIndex: 'ai0102', + key: 'ai0102', + width: 120 + }, + { + title: '招聘专员', + dataIndex: 'zpzy', + key: 'zpzy', + }, + { + title: '入职日期', + dataIndex: 'rzrq', + key: 'rzrq', + }, + { + title: '上岗日期', + dataIndex: 'sgrq', + key: 'sgrq', + }, + { + title: '工作场所', + dataIndex: 'gzcs', + key: 'gzcs', + }, + { + title: '个人爱好', + dataIndex: 'grah', + key: 'grah', + }, + { + title: '工作日期', + dataIndex: 'gzrq', + key: 'gzrq', + }, + { + title: '职称日期', + dataIndex: 'zcrq', + key: 'zcrq', + }, + + { + title: '工作电话', + dataIndex: 'gzdh', + key: 'gzdh', + }, + { + title: 'QQ账号', + dataIndex: 'qqzh', + key: 'qqzh', + }, + { + title: '合同开始', + dataIndex: 'htks', + key: 'htks', + }, + { + title: '合同结束', + dataIndex: 'htks', + key: 'htks', + }, + { + title: '实习开始', + dataIndex: 'sxks', + key: 'sxks', + }, + { + title: '实习结束', + dataIndex: 'sxjs', + key: 'sxjs', + }, + { + title: '试用开始', + dataIndex: 'syks', + key: 'syks', + }, + { + title: '试用结束', + dataIndex: 'syjs', + key: 'syjs', + }, + { + title: '单位工龄', + dataIndex: 'aa0101', + key: 'aa0101', + }, + { + title: '人员类别', + dataIndex: 'aa010d', + key: 'aa010d', + render: val => { + const list = OR || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '合同类别', + dataIndex: 'htlb', + key: 'htlb', + }, + { + title: '招聘来源', + dataIndex: 'zply', + key: 'zply', + }, + { + title: '部门岗位', + dataIndex: 'bmgw', + key: 'bmgw', + }, + { + title: '岗位级别', + dataIndex: 'gwjb', + key: 'gwjb', + }, + { + title: '员工状态', + dataIndex: 'ygzt', + key: 'ygzt', + }, + { + title: '邮箱', + dataIndex: 'yx', + key: 'yx', + }, + { + title: '出生地', + dataIndex: 'aa0117', + key: 'aa0117', + render: val => { + const list = AB || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '离职方式', + dataIndex: 'lzfs', + key: 'lzfs', + }, + { + title: '离职原因', + dataIndex: 'lzyy', + key: 'lzyy', + }, + { + title: '货币编码', + dataIndex: 'aa0115', + key: 'aa0115', + }, + { + title: '参加工作日期', + dataIndex: 'aa0141', + key: 'aa0141', + }, + { + title: '工资类别', + dataIndex: 'aa5510', + key: 'aa5510', + render: val => { + const list = HC || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '扣税标准', + dataIndex: 'ksbz', + key: 'ksbz', + }, + { + title: '进入本单位日期', + dataIndex: 'aa0144', + key: 'aa0144', + width: 120 + }, + + { + title: '工龄', + dataIndex: 'aa0151', + key: 'aa0151', + }, + { + title: '开户银行', + dataIndex: 'khyh', + key: 'khyh', + }, + { + title: '工资账号', + dataIndex: 'ak010e', + key: 'ak010e', + }, + { + title: '港澳台及外籍人士', + dataIndex: 'aa0181', + key: 'aa0181', + width: 130, + render: val => { + const list = OC || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '参加党派时间', + dataIndex: 'cjdpsj', + key: 'cjdpsj', + }, + { + title: '养老账号', + dataIndex: 'ylzh', + key: 'ylzh', + }, + { + title: '医保账号', + dataIndex: 'ybzh', + key: 'ybzh', + }, + { + title: '单位', + dataIndex: 'ab0111', + key: 'ab0111', + }, + { + title: '下属部门', + dataIndex: 'ab0112', + key: 'ab0112', + }, + { + title: '失业账号', + dataIndex: 'syzh', + key: 'syzh', + }, + { + title: '公积金账号', + dataIndex: 'gjjzh', + key: 'gjjzh', + }, + { + title: '特长', + dataIndex: 'ae0103', + key: 'ae0103', + }, + { + title: '减员时间', + dataIndex: 'ae0112', + key: 'ae0112', + }, + { + title: '有效证件类别', + dataIndex: 'ae0117', + key: 'ae0117', + render: val => { + const list = OD || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '成本类别', + dataIndex: 'ak010a', + key: 'ak010a', + render: val => { + const list = X4 || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '工资卡状态', + dataIndex: 'ak010k', + key: 'ak010k', + }, + { + title: '连续工龄', + dataIndex: 'ak010o', + key: 'ak010o', + }, + { + title: '工资卡发放状态', + dataIndex: 'ak010j', + key: 'ak010j', + width: 120 + }, + { + title: '备注', + dataIndex: 'aa0108', + key: 'aa0108', + }, + // { + // title: '照片', + // dataIndex: 'za9998', + // key: 'za9998', + // }, + { + title: '操作', + width: 220, + fixed: 'right', + render: (text, record) => ( + + this.handleViewModalVisible(true, record)}> 查看 + + this.handleUpdateModalVisible(true, record)}> 编辑 + + this.handleDeleteRecord(record)} > + 删除 + + + ), + }, + ]; + + + componentDidMount() { + const { dispatch } = this.props; + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + }); + } + + + + handleStandardTableChange = (pagination, filtersArg, sorter) => { + + const { dispatch } = this.props; + const { formValues } = this.state; + + const params = { + currentPage: pagination.current, + pageSize: pagination.pageSize, + ...formValues, + }; + + if (sorter.field) { + params.sorter = `${sorter.field}_${sorter.order}`; + } + + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: params, + }); + + + }; + + previewItem = record => { + const id = record.id; + //history.push(`/baseinfodata/basic/${id}{id}`); + }; + + handleFormReset = () => { + const { dispatch } = this.props; + this.setState({ + formValues: {}, + }); + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: {}, + }); + + }; + + toggleForm = () => { + + const { expandForm } = this.state; + this.setState({ + expandForm: !expandForm, + }); + }; + + + + handleSelectRows = rows => { + this.setState({ + selectedRows: rows, + }); + const { getChildrenMsg } = this.props; + if(getChildrenMsg){ + getChildrenMsg(rows); + } + }; + + handleSearch = values => { + const { dispatch } = this.props; + this.setState({ + formValues: values, + }); + + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: values, + }); + + }; + + + + + handleModalVisible = flag => { + this.setState({ + modalVisible: !!flag, + }); + }; + + handleUpdateModalVisible = (flag, record) => { + this.setState({ + updateModalVisible: !!flag, + updateFormValues: record || {}, + }); + }; + + handleViewModalVisible = (flag, record) => { + + this.setState({ + viewModalVisible: !!flag, + viewFormValues: record || {}, + }); + + }; + + handleAdd = (fields, form, loading) => { + const { dispatch } = this.props; + if(!!fields.imageFile){ + const formData = new FormData() + formData.append('file', fields.imageFile) + dispatch({ + type: 'baseinfodata/upload_single_file', + payload: formData, + callback: ({ success, datarecord}) => { + if(success){ + fields.za9998 = datarecord.id + this.handleFormData(fields, form, loading) + }else{ + message.error('上传图片失败') + } + } + }) + }else{ + this.handleFormData(fields, form, loading) + } + }; + + handleFormData = (fields, form, loading) => { + const { dispatch } = this.props + dispatch({ + type: 'baseinfodata/insert_for_aa01', + payload: { + id: fields.id,//ID主键 + gh: fields.gh, //工号, + za0101: fields.za0101, //姓名, + aa0107: fields.aa0107, //性别, + za9998: fields.za9998, //照片, + ak010b: fields.ak010b, //手机, + ak010i: fields.ak010i, //电话, + ae0141: fields.ae0141, //年龄, + zjm: fields.zjm, //助记码, + gs: fields.gs, //公司, + bm: fields.bm, //部门, + zw: fields.zw, //职位, + zc: fields.zc, //职称, + xz: fields.xz, //险种, + aa0177: fields.aa0177, //身份证, + kqkh: fields.kqkh, //考勤卡号, + xl: fields.xl, //学历, + aa0127: fields.aa0127, //婚姻状况, + ygmm: fields.ygmm, //员工密码, + qrmm: fields.qrmm, //确认密码, + xw: fields.xw, //学位, + aa0114: fields.aa0114, //籍贯, + aa0111: formatDate(fields.aa0111,"YYYY-MM-DD HH:mm:ss"), //出生日期, + aa0121: fields.aa0121, //民族, + aa2205: fields.aa2205, //政治面貌, + lb: fields.lb, //类别, + byxx: fields.byxx, //毕业学校, + sxzy: fields.sxzy, //所学专业, + byrq: fields.byrq, //毕业日期, + jbgz: fields.jbgz, //基本工资, + ak010n: fields.ak010n, //家庭住址, + ak010m: fields.ak010m, //户口地址, + wxzh: fields.wxzh, //微信账号, + dadz: fields.dadz, //档案地址, + lxr: fields.lxr, //联系人, + lxrdh: fields.lxrdh, //联系人电话, + ae010m: fields.ae010m, //紧急联系人, + ai0102: fields.ai0102, //紧急联系人电话, + zpzy: fields.zpzy, //招聘专员 + rzrq: fields.rzrq, //入职日期 + sgrq: fields.sgrq, //上岗日期 + gzcs: fields.gzcs, //工作场所 + grah: fields.grah, //个人爱好 + gzrq: fields.gzrq, //工作日期 + zcrq: fields.zcrq, //职称日期 + gzdh: fields.gzdh, //工作电话 + qqzh: fields.qqzh, //QQ账号 + htks: fields.htks, //合同开始 + htjs: fields.htjs, //合同结束 + sxks: fields.sxks, //实习开始 + sxjs: fields.sxjs, //实习结束 + syks: fields.syks, //试用开始 + syjs: fields.syjs, //试用结束 + aa0101: fields.aa0101, //单位工龄 + aa010d: fields.aa010d, //人员类别 + htlb: fields.htlb, //合同类别 + zply: fields.zply, //招聘来源 + bmgw: fields.bmgw, //部门岗位 + gwjb: fields.gwjb, //岗位级别 + ygzt: fields.ygzt, //员工状态 + lzrq: fields.lzrq, //离职日期 + yx: fields.yx, //邮箱 + aa0117: fields.aa0117, //出生地 + lzfs: fields.lzfs, //离职方式 + lzyy: fields.lzyy, //离职原因 + aa0115: fields.aa0115, //货币编码 + aa0141: fields.aa0141, //参加工作日期 + aa5510: fields.aa5510, //工资类别 + ksbz: fields.ksbz, //扣税标准 + aa0144: fields.aa0144, //进入本单位日期 + aa0151: fields.aa0151, //工龄 + khyh: fields.khyh, //开户银行 + ak010e: fields.ak010e, //工资账号 + aa0181: fields.aa0181, //港澳台及外籍人士 + cjdpsj: fields.cjdpsj, //参加党派时间 + ylzh: fields.ylzh, //养老账号 + ybzh: fields.ybzh, //医保账号 + ab0111: fields.ab0111, //单位 + ab0112: fields.ab0112, //下属部门 + syzh: fields.syzh, //失业账号 + gjjzh: fields.gjjzh, //公积金账号 + ae0103: fields.ae0103, //特长 + ae0112: fields.ae0112, //减员时间 + ae0117: fields.ae0117, //有效证件类别 + ak010a: fields.ak010a, //成本类别 + ak010k: fields.ak010k, //工资卡状态 + ak010o: fields.ak010o, //连续工龄 + ak010j: fields.ak010j, //工资卡发放状态 + aa0108: fields.aa0108, //备注 + }, + callback: (res) => { + loading(false); + if (res.success == true) { + message.success('添加成功'); + form.resetFields(); + this.handleModalVisible(); + }else{ + loading(false); + } + } + }); + } + + + handleDeleteRecord = record => { + + const { dispatch } = this.props; + + dispatch({ + type: 'baseinfodata/delete_by_primarykey_for_aa01', + payload: { + recordid: record.id, + }, + callback: () => { + this.setState({ + selectedRows: [], + }); + }, + }); + + message.success('删除成功'); + + + }; + + handleUpdate = (fields, form, loading) => { + + const { dispatch } = this.props; + if(!!fields.imageFile){ + const formData = new FormData() + formData.append('file', fields.imageFile) + dispatch({ + type: 'baseinfodata/upload_single_file', + payload: formData, + callback: ({ success, datarecord}) => { + if(success){ + fields.za9998 = datarecord.id + this.handleUpdateData(fields, form, loading) + }else{ + message.error('上传图片失败') + } + } + }) + }else{ + this.handleUpdateData(fields, form, loading) + } + }; + + handleUpdateData = (fields, form, loading) => { + const { dispatch } = this.props + dispatch({ + type: 'baseinfodata/update_for_aa01', + payload: { + id: fields.id,//ID主键 + gh: fields.gh, //工号, + za0101: fields.za0101, //姓名, + aa0107: fields.aa0107, //性别, + za9998: fields.za9998, //照片, + ak010b: fields.ak010b, //手机, + ak010i: fields.ak010i, //电话, + ae0141: fields.ae0141, //年龄, + zjm: fields.zjm, //助记码, + gs: fields.gs, //公司, + bm: fields.bm, //部门, + zw: fields.zw, //职位, + zc: fields.zc, //职称, + xz: fields.xz, //险种, + aa0177: fields.aa0177, //身份证, + kqkh: fields.kqkh, //考勤卡号, + xl: fields.xl, //学历, + aa0127: fields.aa0127, //婚姻状况, + ygmm: fields.ygmm, //员工密码, + qrmm: fields.qrmm, //确认密码, + xw: fields.xw, //学位, + aa0114: fields.aa0114, //籍贯, + aa0111: fields.aa0111, //出生日期, + aa0121: fields.aa0121, //民族, + aa2205: fields.aa2205, //政治面貌, + lb: fields.lb, //类别, + byxx: fields.byxx, //毕业学校, + sxzy: fields.sxzy, //所学专业, + byrq: fields.byrq, //毕业日期, + jbgz: fields.jbgz, //基本工资, + ak010n: fields.ak010n, //家庭住址, + ak010m: fields.ak010m, //户口地址, + wxzh: fields.wxzh, //微信账号, + dadz: fields.dadz, //档案地址, + lxr: fields.lxr, //联系人, + lxrdh: fields.lxrdh, //联系人电话, + ae010m: fields.ae010m, //紧急联系人, + ai0102: fields.ai0102, //紧急联系人电话, + zpzy: fields.zpzy, //招聘专员 + rzrq: fields.rzrq, //入职日期 + sgrq: fields.sgrq, //上岗日期 + gzcs: fields.gzcs, //工作场所 + grah: fields.grah, //个人爱好 + gzrq: fields.gzrq, //工作日期 + zcrq: fields.zcrq, //职称日期 + gzdh: fields.gzdh, //工作电话 + qqzh: fields.qqzh, //QQ账号 + htks: fields.htks, //合同开始 + htjs: fields.htjs, //合同结束 + sxks: fields.sxks, //实习开始 + sxjs: fields.sxjs, //实习结束 + syks: fields.syks, //试用开始 + syjs: fields.syjs, //试用结束 + aa0101: fields.aa0101, //单位工龄 + aa010d: fields.aa010d, //人员类别 + htlb: fields.htlb, //合同类别 + zply: fields.zply, //招聘来源 + bmgw: fields.bmgw, //部门岗位 + gwjb: fields.gwjb, //岗位级别 + ygzt: fields.ygzt, //员工状态 + lzrq: fields.lzrq, //离职日期 + yx: fields.yx, //邮箱 + aa0117: fields.aa0117, //出生地 + lzfs: fields.lzfs, //离职方式 + lzyy: fields.lzyy, //离职原因 + aa0115: fields.aa0115, //货币编码 + aa0141: fields.aa0141, //参加工作日期 + aa5510: fields.aa5510, //工资类别 + ksbz: fields.ksbz, //扣税标准 + aa0144: fields.aa0144, //进入本单位日期 + aa0151: fields.aa0151, //工龄 + khyh: fields.khyh, //开户银行 + ak010e: fields.ak010e, //工资账号 + aa0181: fields.aa0181, //港澳台及外籍人士 + cjdpsj: fields.cjdpsj, //参加党派时间 + ylzh: fields.ylzh, //养老账号 + ybzh: fields.ybzh, //医保账号 + ab0111: fields.ab0111, //单位 + ab0112: fields.ab0112, //下属部门 + syzh: fields.syzh, //失业账号 + gjjzh: fields.gjjzh, //公积金账号 + ae0103: fields.ae0103, //特长 + ae0112: fields.ae0112, //减员时间 + ae0117: fields.ae0117, //有效证件类别 + ak010a: fields.ak010a, //成本类别 + ak010k: fields.ak010k, //工资卡状态 + ak010o: fields.ak010o, //连续工龄 + ak010j: fields.ak010j, //工资卡发放状态 + aa0108: fields.aa0108, //备注 + fileId: fields?.fileId ?? '', + filePath: fields?.filePath ?? '', + }, + callback: (res) => { + loading(false); + if (res.success == true) { + message.success('修改成功'); + form.resetFields(); + this.handleUpdateModalVisible(); + }else{ + loading(false) + } + } + }); + } + + renderSimpleForm() { + + const parentMethods = { + handleSearch: this.handleSearch, + handleFormReset: this.handleFormReset, + toggleForm: this.toggleForm, + submitButtons: styles.submitButtons, + }; + + return ( + + + + ); + + } + + + + renderAdvancedForm() { + + const parentMethods = { + handleSearch: this.handleSearch, + handleFormReset: this.handleFormReset, + toggleForm: this.toggleForm, + }; + + return ( + + + ); + + } + + renderForm() { + const { expandForm } = this.state; + return expandForm ? this.renderAdvancedForm() : this.renderSimpleForm(); + } + + render() { + + const { + baseinfodata: { data }, + loading, + } = this.props; + + + const { selectedRows, modalVisible, updateModalVisible, viewModalVisible, updateFormValues, viewFormValues } = this.state; + const menu = ( + + 删除 + 批量审批 + + ); + const parentMethods = { + handleAdd: this.handleAdd, + handleModalVisible: this.handleModalVisible, + }; + const updateMethods = { + handleUpdateModalVisible: this.handleUpdateModalVisible, + handleUpdate: this.handleUpdate, + }; + const viewMethods = { + handleViewModalVisible: this.handleViewModalVisible + }; + return ( + + <> + +
    + +
    {this.renderForm()}
    + + +
    + + + {selectedRows.length > 0 && ( + + + + + + + )} +
    + + +
    +
    + + + + + {updateFormValues && Object.keys(updateFormValues).length ? ( + + ) : null} + + + {viewFormValues && Object.keys(viewFormValues).length ? ( + + ) : null} + + + + ); + } +} + + +export default Aa01Table; + diff --git a/src/components/BusinessBase/form/Aa01CreateForm.js b/src/components/BusinessBase/form/Aa01CreateForm.js new file mode 100644 index 0000000..818b613 --- /dev/null +++ b/src/components/BusinessBase/form/Aa01CreateForm.js @@ -0,0 +1,900 @@ +import React, { useState } from 'react'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { + Form, + Input, + DatePicker, + Modal, + Button, + Upload +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { MyIcon } from "@/components/Icon" +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import moment from 'moment'; +import styles from '@/global.less'; +import style from '../index.less'; + +const FormItem = Form.Item; + +const Aa01CreateForm = (props) => { + const [form] = Form.useForm(); + const { modalVisible, handleAdd, handleModalVisible, dictDataList } = props; + const [error, setError] = useState([]); + const [subLoading, setSubLoading] = useState(false); + const [imageUrl, setImageUrl] = useState(''); + const [imageFile, setImageFile] = useState({}); + const okHandle = () => { + + form + .validateFields() + .then(fieldsValue => { + if(imageFile){ + fieldsValue.imageFile = imageFile + } + setSubLoading(true); + setError([]); + handleAdd(fieldsValue, form, setSubLoading); + setImageUrl('') + setImageFile({}) + }) + .catch(errInfo => { + onFinishFailed(errInfo) + }); + + }; + + const onFinishFailed = (errorInfo) => { + setError(errorInfo.errorFields) + } + + const getErrorInfo = (errors) => { + const errorCount = errors.filter((item) => item.errors.length > 0).length; + if (!errors || errorCount === 0) { + return null; + } + + return ( + + {errorCount} + + ); + }; + + const beforeUpload = file => { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg'; + if (!isJpgOrPng) { + message.error('只能上传 JPG/JPEG/PNG 格式的文件!'); + } + const isLt2M = file.size / 1024 / 1024 < 2; + if (!isLt2M) { + message.error('图像必须小于2MB!'); + } + return isJpgOrPng && isLt2M; + } + + const handleChange = info => { + if (info.file.status === 'done') { + // Get this url from response in real world. + if (window.FileReader) { + const reader = new FileReader(); + reader.readAsDataURL(info.file.originFileObj); + reader.addEventListener('load', () => { + setImageUrl(reader.result); + }); + } + + setImageFile(info.file?.originFileObj); + } + }; + + const uploadButton = (); + + return ( + handleModalVisible()} + afterClose={() => handleModalVisible()} + footer={<>{getErrorInfo(error)} + + } + > +
    + + + + + + + + + + + + + + {/* 照片(za9998) */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    人员基本情况信息
    + 工号 + + + + + + 姓名 + + + + + + 性别 + + + + + +
    + + {imageUrl ? 图片加载失败 : uploadButton} + +
    上传照片
    +
    +
    + 手机 + + + + + + 电话 + + + + + + 年龄 + + + + +
    + 助记码 + + + + + + 公司 + + + + + + 部门 + + + + +
    + 职位 + + + + + + 职称 + + + + + + 险种 + + + + +
    + 身份证 + + + + + + 考勤卡号 + + + + + + 学历 + + + + + + 婚姻状况 + + + + +
    + 员工密码 + + + + + + 确认密码 + + + + + + 学位 + + + + + + 籍贯 + + + + +
    + 出生日期 + + + + + + 民族 + + + + + + 政治面貌 + + + + + + 类别 + + + + +
    + 毕业学校 + + + + + + 所学专业 + + + + + + 毕业日期 + + + + + + 基本工资 + + + + +
    + 家庭住址 + + + + + + 户口地址 + + + + +
    + 微信账号 + + + + + + 档案地址 + + + + +
    + 联系人 + + + + + + 联系人电话 + + + + + + 紧急联系人 + + + + + + 紧急联系人电话 + + + + +
    + 招聘专员 + + + + + + 职工类别 + + + + + + 入职日期 + + + + + + 上岗日期 + + + + +
    + 工作场所 + + + + + + 个人爱好 + + + + + + 工作日期 + + + + + + 职称日期 + + + + +
    + 工作电话 + + + + + + QQ账号 + + + + + + 合同开始 + + + + + + 合同结束 + + + + +
    + 实习开始 + + + + + + 实习结束 + + + + + + 试用开始 + + + + + + 试用结束 + + + + +
    + 单位工龄 + + + + + + 人员类别 + + + + + + 合同类别 + + + + + + 招聘来源 + + + + +
    + 部门岗位 + + + + + + 岗位级别 + + + + + + 员工状态 + + + + + + 离职日期 + + + + +
    + 邮箱 + + + + + + 出生地 + + + + + + 离职方式 + + + + + + 离职原因 + + + + +
    + 货币编码 + + + + + + 参加工作日期 + + + + + + 工资类别 + + + + + + 扣税标准 + + + + +
    + 进入本单位日期 + + + + + + 工龄 + + + + + + 开户银行 + + + + + + 工资账号 + + + + +
    + 港澳台及外籍人士 + + + + + + 参加党派时间 + + + + + + 养老账号 + + + + + + 医保账号 + + + + +
    + 单位 + + + + + + 下属部门 + + + + + + 失业账号 + + + + + + 公积金账号 + + + + +
    + 特长 + + + + + + 减员时间 + + + + + + 有效证件类别 + + + + + + 成本类别 + + + + +
    + 工资卡状态 + + + + + + 连续工龄 + + + + + + 工资卡发放状态 + + + + + + +
    + 备注 + + + + +
    +
    +
    + ); +}; + +export default Aa01CreateForm; + diff --git a/src/components/BusinessBase/form/Aa01RenderAdvancedForm.js b/src/components/BusinessBase/form/Aa01RenderAdvancedForm.js new file mode 100644 index 0000000..f61446c --- /dev/null +++ b/src/components/BusinessBase/form/Aa01RenderAdvancedForm.js @@ -0,0 +1,98 @@ +import { + Row, + Col, + Form, + Input, + DatePicker, + Button, +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { AX, OR, } from '@/utils/pr_new_datadictionary'; +import { UpOutlined } from '@ant-design/icons'; +import style from "@/global.less"; +const FormItem = Form.Item; + +const Aa01RenderAdvancedForm = (props) => { + + const [form] = Form.useForm(); + + const { handleSearch, handleFormReset, toggleForm } = props; + + const onFinish = values => { + //console.log('Received values of form: ', values); + handleSearch(values); + }; + + const myhandleFormReset = () => { + form.resetFields(); + handleFormReset(); + }; + + return ( + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + 收起 + +
    + + +
    + +
    + + ); + + +}; + +export default Aa01RenderAdvancedForm; diff --git a/src/components/BusinessBase/form/Aa01RenderSimpleForm.js b/src/components/BusinessBase/form/Aa01RenderSimpleForm.js new file mode 100644 index 0000000..21ef9ed --- /dev/null +++ b/src/components/BusinessBase/form/Aa01RenderSimpleForm.js @@ -0,0 +1,70 @@ +import { + Row, + Col, + Form, + Input, + DatePicker, + Button, +} from 'antd'; +import { DownOutlined } from '@ant-design/icons'; +import style from "@/global.less"; +const FormItem = Form.Item; + +const Aa01RenderSimpleForm = (props) => { + + const [form] = Form.useForm(); + + const { submitButtons, handleSearch, handleFormReset, toggleForm } = props; + + const onFinish = values => { + //console.log('Received values of form: ', values); + handleSearch(values); + }; + + const myhandleFormReset = () => { + form.resetFields(); + handleFormReset(); + }; + + return ( + +
    + + + + + + + + + + + + + + + + + +
    + + + + 展开 + +
    + +
    + +
    + + ); + + +}; + +export default Aa01RenderSimpleForm; \ No newline at end of file diff --git a/src/components/BusinessBase/form/Aa01UpdateForm.js b/src/components/BusinessBase/form/Aa01UpdateForm.js new file mode 100644 index 0000000..0682cc1 --- /dev/null +++ b/src/components/BusinessBase/form/Aa01UpdateForm.js @@ -0,0 +1,1005 @@ +import React, { useState } from 'react'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { + Form, + Input, + DatePicker, + Modal, + Button, + Upload +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { MyIcon } from "@/components/Icon" +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import moment from 'moment'; +import styles from '@/global.less'; +import style from '../index.less'; + +const FormItem = Form.Item; + +const Aa01UpdateForm = (props) => { + + const { handleUpdate, updateModalVisible, handleUpdateModalVisible, values, dictDataList } = props; + const [form] = Form.useForm(); + const [recordId, setRecordId] = useState(''); + const [error, setError] = useState([]); + const [subLoading, setSubLoading] = useState(false); + const [imageUrl, setImageUrl] = useState(''); + const [imageFile, setImageFile] = useState({}); + const [image,setImage] = useState({}) + React.useEffect(() => { + setRecordId(values?.id); + if(!!values.zp){ + setImage(values.zp) + setImageUrl(values.zp.fileurl) + } + form?.setFieldsValue({ + id: values.id,//ID主键 + gh: values.gh, //工号, + za0101: values.za0101, //姓名, + aa0107: values.aa0107, //性别, + za9998: values.za9998, //照片, + ak010b: values.ak010b, //手机, + ak010i: values.ak010i, //电话, + ae0141: values.ae0141, //年龄, + zjm: values.zjm, //助记码, + gs: values.gs, //公司, + bm: values.bm, //部门, + zw: values.zw, //职位, + zc: values.zc, //职称, + xz: values.xz, //险种, + aa0177: values.aa0177, //身份证, + kqkh: values.kqkh, //考勤卡号, + xl: values.xl, //学历, + aa0127: values.aa0127, //婚姻状况, + ygmm: values.ygmm, //员工密码, + qrmm: values.qrmm, //确认密码, + xw: values.xw, //学位, + aa0114: values.aa0114, //籍贯, + aa0111: values?.aa0111 ? moment(values.aa0111, 'YYYY-MM-DD') : '', //出生日期, + aa0121: values.aa0121, //民族, + aa2205: values.aa2205, //政治面貌, + lb: values.lb, //类别, + byxx: values.byxx, //毕业学校, + sxzy: values.sxzy, //所学专业, + byrq: values?.byrq ? moment(values.byrq, 'YYYY-MM-DD') : '', //毕业日期, + jbgz: values.jbgz, //基本工资, + ak010n: values.ak010n, //家庭住址, + ak010m: values.ak010m, //户口地址, + wxzh: values.wxzh, //微信账号, + dadz: values.dadz, //档案地址, + lxr: values.lxr, //联系人, + lxrdh: values.lxrdh, //联系人电话, + ae010m: values.ae010m, //紧急联系人, + ai0102: values.ai0102, //紧急联系人电话, + zpzy: values.zpzy, //招聘专员 + rzrq: values?.rzrq ? moment(values.rzrq, 'YYYY-MM-DD') : '', //入职日期 + sgrq: values?.sgrq ? moment(values.sgrq, 'YYYY-MM-DD') : '', //上岗日期 + gzcs: values.gzcs, //工作场所 + grah: values.grah, //个人爱好 + gzrq: values?.gzrq ? moment(values.gzrq, 'YYYY-MM-DD') : '', //工作日期 + zcrq: values?.zcrq ? moment(values.zcrq, 'YYYY-MM-DD') : '', //职称日期 + gzdh: values.gzdh, //工作电话 + qqzh: values.qqzh, //QQ账号 + htks: values.htks, //合同开始 + htjs: values.htjs, //合同结束 + sxks: values.sxks, //实习开始 + sxjs: values.sxjs, //实习结束 + syks: values.syks, //试用开始 + syjs: values.syjs, //试用结束 + aa0101: values.aa0101, //单位工龄 + aa010d: values.aa010d, //人员类别 + htlb: values.htlb, //合同类别 + zply: values.zply, //招聘来源 + bmgw: values.bmgw, //部门岗位 + gwjb: values.gwjb, //岗位级别 + ygzt: values.ygzt, //员工状态 + lzrq: values?.lzrq ? moment(values.lzrq, 'YYYY-MM-DD') : '', //离职日期 + yx: values.yx, //邮箱 + aa0117: values.aa0117, //出生地 + lzfs: values.lzfs, //离职方式 + lzyy: values.lzyy, //离职原因 + aa0115: values.aa0115, //货币编码 + aa0141: values?.aa0141 ? moment(values.aa0141, 'YYYY-MM-DD') : '', //参加工作日期 + aa5510: values.aa5510, //工资类别 + ksbz: values.ksbz, //扣税标准 + aa0144: values?.aa0144 ? moment(values.aa0144, 'YYYY-MM-DD') : '', //进入本单位日期 + aa0151: values.aa0151, //工龄 + khyh: values.khyh, //开户银行 + ak010e: values.ak010e, //工资账号 + aa0181: values.aa0181, //港澳台及外籍人士 + cjdpsj: values?.cjdpsj ? moment(values.cjdpsj, 'YYYY-MM-DD') : '', //参加党派时间 + ylzh: values.ylzh, //养老账号 + ybzh: values.ybzh, //医保账号 + ab0111: values.ab0111, //单位 + ab0112: values.ab0112, //下属部门 + syzh: values.syzh, //失业账号 + gjjzh: values.gjjzh, //公积金账号 + ae0103: values.ae0103, //特长 + ae0112: values?.ae0112 ? moment(values.ae0112, 'YYYY-MM-DD') : '', //减员时间 + ae0117: values.ae0117, //有效证件类别 + ak010a: values.ak010a, //成本类别 + ak010k: values.ak010k, //工资卡状态 + ak010o: values.ak010o, //连续工龄 + ak010j: values.ak010j, //工资卡发放状态 + aa0108: values.aa0108, //备注 + }); + }, [values, form]); + + const handleLocalUpdate = () => { + + form + .validateFields() + .then(fieldsValue => { + setSubLoading(true); + setError([]); + if(imageFile){ + fieldsValue.imageFile = imageFile + fieldsValue.fileId = image.id + fieldsValue.filePath = image.filepath + } + fieldsValue.id = recordId; + fieldsValue.a1021_start_time = fieldsValue.a1021_start_time ? moment(fieldsValue.a1021_start_time).format("YYYY-MM-DD") : null; + handleUpdate(fieldsValue, form, setSubLoading); + setImage({}) + setImageFile({}) + setImageUrl('') + }) + .catch(errInfo => { + onFinishFailed(errInfo) + }); + + }; + + const onFinishFailed = (errorInfo) => { + setError(errorInfo.errorFields) + } + + const getErrorInfo = (errors) => { + const errorCount = errors.filter((item) => item.errors.length > 0).length; + if (!errors || errorCount === 0) { + return null; + } + + return ( + + {errorCount} + + ); + }; + const beforeUpload = file => { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg'; + if (!isJpgOrPng) { + message.error('只能上传 JPG/JPEG/PNG 格式的文件!'); + } + const isLt2M = file.size / 1024 / 1024 < 2; + if (!isLt2M) { + message.error('图像必须小于2MB!'); + } + return isJpgOrPng && isLt2M; + } + + const handleChange = info => { + if (info.file.status === 'done') { + // Get this url from response in real world. + if (window.FileReader) { + const reader = new FileReader(); + reader.readAsDataURL(info.file.originFileObj); + reader.addEventListener('load', () => { + setImageUrl(reader.result); + }); + } + setImageFile(info.file?.originFileObj); + } + }; + + const uploadButton = (); + + return ( + handleLocalUpdate()} + onCancel={() => handleUpdateModalVisible()} + footer={<>{getErrorInfo(error)} + + } + > +
    + + + + + + + + + + + + + {/* 照片(za9998) */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    人员基本情况信息
    + 工号 + + + + + + 姓名 + + + + + + 性别 + + + + + +
    + + {imageUrl ? 图片加载失败 : uploadButton} + +
    上传照片
    +
    +
    + 手机 + + + + + + 电话 + + + + + + 年龄 + + + + +
    + 助记码 + + + + + + 公司 + + + + + + 部门 + + + + +
    + 职位 + + + + + + 职称 + + + + + + 险种 + + + + +
    + 身份证 + + + + + + 考勤卡号 + + + + + + 学历 + + + + + + 婚姻状况 + + + + +
    + 员工密码 + + + + + + 确认密码 + + + + + + 学位 + + + + + + 籍贯 + + + + +
    + 出生日期 + + + + + + 民族 + + + + + + 政治面貌 + + + + + + 类别 + + + + +
    + 毕业学校 + + + + + + 所学专业 + + + + + + 毕业日期 + + + + + + 基本工资 + + + + +
    + 家庭住址 + + + + + + 户口地址 + + + + +
    + 微信账号 + + + + + + 档案地址 + + + + +
    + 联系人 + + + + + + 联系人电话 + + + + + + 紧急联系人 + + + + + + 紧急联系人电话 + + + + +
    + 招聘专员 + + + + + + 职工类别 + + + + + + 入职日期 + + + + + + 上岗日期 + + + + +
    + 工作场所 + + + + + + 个人爱好 + + + + + + 工作日期 + + + + + + 职称日期 + + + + +
    + 工作电话 + + + + + + QQ账号 + + + + + + 合同开始 + + + + + + 合同结束 + + + + +
    + 实习开始 + + + + + + 实习结束 + + + + + + 试用开始 + + + + + + 试用结束 + + + + +
    + 单位工龄 + + + + + + 人员类别 + + + + + + 合同类别 + + + + + + 招聘来源 + + + + +
    + 部门岗位 + + + + + + 岗位级别 + + + + + + 员工状态 + + + + + + 离职日期 + + + + +
    + 邮箱 + + + + + + 出生地 + + + + + + 离职方式 + + + + + + 离职原因 + + + + +
    + 货币编码 + + + + + + 参加工作日期 + + + + + + 工资类别 + + + + + + 扣税标准 + + + + +
    + 进入本单位日期 + + + + + + 工龄 + + + + + + 开户银行 + + + + + + 工资账号 + + + + +
    + 港澳台及外籍人士 + + + + + + 参加党派时间 + + + + + + 养老账号 + + + + + + 医保账号 + + + + +
    + 单位 + + + + + + 下属部门 + + + + + + 失业账号 + + + + + + 公积金账号 + + + + +
    + 特长 + + + + + + 减员时间 + + + + + + 有效证件类别 + + + + + + 成本类别 + + + + +
    + 工资卡状态 + + + + + + 连续工龄 + + + + + + 工资卡发放状态 + + + + + + +
    + 备注 + + + + +
    +
    + +
    + ); + +} + +export default Aa01UpdateForm; diff --git a/src/components/BusinessBase/form/Aa01ViewForm.js b/src/components/BusinessBase/form/Aa01ViewForm.js new file mode 100644 index 0000000..4dccddc --- /dev/null +++ b/src/components/BusinessBase/form/Aa01ViewForm.js @@ -0,0 +1,911 @@ +import { + Form, + Input, + DatePicker, + Modal, + Upload +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import userAvatar from '@/assets/img/user_avatar.png'; +import styles from "@/global.less"; +import moment from 'moment'; +import style from '../index.less'; +import React, { useState } from 'react'; +const FormItem = Form.Item; + +const Aa01ViewForm = (props) => { + + const { viewModalVisible, handleViewModalVisible, values } = props; + + const [form] = Form.useForm(); + const photoUrl = values?.zp?.fileurl ?? ''; + React.useEffect(() => { + form?.setFieldsValue({ + id: values.id,//ID主键 + gh: values.gh, //工号, + za0101: values.za0101, //姓名, + aa0107: values.aa0107, //性别, + za9998: values.za9998, //照片, + ak010b: values.ak010b, //手机, + ak010i: values.ak010i, //电话, + ae0141: values.ae0141, //年龄, + zjm: values.zjm, //助记码, + gs: values.gs, //公司, + bm: values.bm, //部门, + zw: values.zw, //职位, + zc: values.zc, //职称, + xz: values.xz, //险种, + aa0177: values.aa0177, //身份证, + kqkh: values.kqkh, //考勤卡号, + xl: values.xl, //学历, + aa0127: values.aa0127, //婚姻状况, + ygmm: values.ygmm, //员工密码, + qrmm: values.qrmm, //确认密码, + xw: values.xw, //学位, + aa0114: values.aa0114, //籍贯, + aa0111: values?.aa0111 ? moment(values.aa0111, 'YYYY-MM-DD') : '', //出生日期, + aa0121: values.aa0121, //民族, + aa2205: values.aa2205, //政治面貌, + lb: values.lb, //类别, + byxx: values.byxx, //毕业学校, + sxzy: values.sxzy, //所学专业, + byrq: values?.byrq ? moment(values.byrq, 'YYYY-MM-DD') : '', //毕业日期, + jbgz: values.jbgz, //基本工资, + ak010n: values.ak010n, //家庭住址, + ak010m: values.ak010m, //户口地址, + wxzh: values.wxzh, //微信账号, + dadz: values.dadz, //档案地址, + lxr: values.lxr, //联系人, + lxrdh: values.lxrdh, //联系人电话, + ae010m: values.ae010m, //紧急联系人, + ai0102: values.ai0102, //紧急联系人电话, + zpzy: values.zpzy, //招聘专员 + rzrq: values?.rzrq ? moment(values.rzrq, 'YYYY-MM-DD') : '', //入职日期 + sgrq: values?.sgrq ? moment(values.sgrq, 'YYYY-MM-DD') : '', //上岗日期 + gzcs: values.gzcs, //工作场所 + grah: values.grah, //个人爱好 + gzrq: values?.gzrq ? moment(values.gzrq, 'YYYY-MM-DD') : '', //工作日期 + zcrq: values?.zcrq ? moment(values.zcrq, 'YYYY-MM-DD') : '', //职称日期 + gzdh: values.gzdh, //工作电话 + qqzh: values.qqzh, //QQ账号 + htks: values.htks, //合同开始 + htjs: values.htjs, //合同结束 + sxks: values.sxks, //实习开始 + sxjs: values.sxjs, //实习结束 + syks: values.syks, //试用开始 + syjs: values.syjs, //试用结束 + aa0101: values.aa0101, //单位工龄 + aa010d: values.aa010d, //人员类别 + htlb: values.htlb, //合同类别 + zply: values.zply, //招聘来源 + bmgw: values.bmgw, //部门岗位 + gwjb: values.gwjb, //岗位级别 + ygzt: values.ygzt, //员工状态 + lzrq: values?.lzrq ? moment(values.lzrq, 'YYYY-MM-DD') : '', //离职日期 + yx: values.yx, //邮箱 + aa0117: values.aa0117, //出生地 + lzfs: values.lzfs, //离职方式 + lzyy: values.lzyy, //离职原因 + aa0115: values.aa0115, //货币编码 + aa0141: values?.aa0141 ? moment(values.aa0141, 'YYYY-MM-DD') : '', //参加工作日期 + aa5510: values.aa5510, //工资类别 + ksbz: values.ksbz, //扣税标准 + aa0144: values?.aa0144 ? moment(values.aa0144, 'YYYY-MM-DD') : '', //进入本单位日期 + aa0151: values.aa0151, //工龄 + khyh: values.khyh, //开户银行 + ak010e: values.ak010e, //工资账号 + aa0181: values.aa0181, //港澳台及外籍人士 + cjdpsj: values?.cjdpsj ? moment(values.cjdpsj, 'YYYY-MM-DD') : '', //参加党派时间 + ylzh: values.ylzh, //养老账号 + ybzh: values.ybzh, //医保账号 + ab0111: values.ab0111, //单位 + ab0112: values.ab0112, //下属部门 + syzh: values.syzh, //失业账号 + gjjzh: values.gjjzh, //公积金账号 + ae0103: values.ae0103, //特长 + ae0112: values?.ae0112 ? moment(values.ae0112, 'YYYY-MM-DD') : '', //减员时间 + ae0117: values.ae0117, //有效证件类别 + ak010a: values.ak010a, //成本类别 + ak010k: values.ak010k, //工资卡状态 + ak010o: values.ak010o, //连续工龄 + ak010j: values.ak010j, //工资卡发放状态 + aa0108: values.aa0108, //备注 + }); + }, [values, form]); + + + return ( + handleViewModalVisible(false, values)} + onCancel={() => handleViewModalVisible(false, values)} + afterClose={() => handleViewModalVisible()} + > + +
    + + + + + + + + + + + + + {/* 照片(za9998) */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    人员基本情况信息
    + 工号 + + + + + + 姓名 + + + + + + 性别 + + + + + +
    + avatar +
    +
    + 手机 + + + + + + 电话 + + + + + + 年龄 + + + + +
    + 助记码 + + + + + + 公司 + + + + + + 部门 + + + + +
    + 职位 + + + + + + 职称 + + + + + + 险种 + + + + +
    + 身份证 + + + + + + 考勤卡号 + + + + + + 学历 + + + + + + 婚姻状况 + + + + +
    + 员工密码 + + + + + + 确认密码 + + + + + + 学位 + + + + + + 籍贯 + + + + +
    + 出生日期 + + + + + + 民族 + + + + + + 政治面貌 + + + + + + 类别 + + + + +
    + 毕业学校 + + + + + + 所学专业 + + + + + + 毕业日期 + + + + + + 基本工资 + + + + +
    + 家庭住址 + + + + + + 户口地址 + + + + +
    + 微信账号 + + + + + + 档案地址 + + + + +
    + 联系人 + + + + + + 联系人电话 + + + + + + 紧急联系人 + + + + + + 紧急联系人电话 + + + + +
    + 招聘专员 + + + + + + 职工类别 + + + + + + 入职日期 + + + + + + 上岗日期 + + + + +
    + 工作场所 + + + + + + 个人爱好 + + + + + + 工作日期 + + + + + + 职称日期 + + + + +
    + 工作电话 + + + + + + QQ账号 + + + + + + 合同开始 + + + + + + 合同结束 + + + + +
    + 实习开始 + + + + + + 实习结束 + + + + + + 试用开始 + + + + + + 试用结束 + + + + +
    + 单位工龄 + + + + + + 人员类别 + + + + + + 合同类别 + + + + + + 招聘来源 + + + + +
    + 部门岗位 + + + + + + 岗位级别 + + + + + + 员工状态 + + + + + + 离职日期 + + + + +
    + 邮箱 + + + + + + 出生地 + + + + + + 离职方式 + + + + + + 离职原因 + + + + +
    + 货币编码 + + + + + + 参加工作日期 + + + + + + 工资类别 + + + + + + 扣税标准 + + + + +
    + 进入本单位日期 + + + + + + 工龄 + + + + + + 开户银行 + + + + + + 工资账号 + + + + +
    + 港澳台及外籍人士 + + + + + + 参加党派时间 + + + + + + 养老账号 + + + + + + 医保账号 + + + + +
    + 单位 + + + + + + 下属部门 + + + + + + 失业账号 + + + + + + 公积金账号 + + + + +
    + 特长 + + + + + + 减员时间 + + + + + + 有效证件类别 + + + + + + 成本类别 + + + + +
    + 工资卡状态 + + + + + + 连续工龄 + + + + + + 工资卡发放状态 + + + + + + +
    + 备注 + + + + +
    +
    + +
    + ); + +} + +export default Aa01ViewForm; \ No newline at end of file diff --git a/src/components/BusinessBase/index.less b/src/components/BusinessBase/index.less new file mode 100644 index 0000000..f77fe23 --- /dev/null +++ b/src/components/BusinessBase/index.less @@ -0,0 +1,83 @@ +.aa01tableList { + .aa01tableListOperator { + margin-bottom: 16px; + button { + margin-right: 8px; + } + } +} + +.aa01tableListForm { + :global { + .ant-form-item { + display: flex; + margin-right: 0; + margin-bottom: 24px; + > .ant-form-item-label { + width: auto; + padding-right: 8px; + line-height: 32px; + } + .ant-form-item-control { + line-height: 32px; + } + } + .ant-form-item-control-wrapper { + flex: 1; + } + } + .submitButtons { + display: block; + margin-bottom: 24px; + white-space: nowrap; + } +} + +.icon { + width: 0.8em; + height: 0.8em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +.ant-upload{ + width: 130px !important; + height: 140px !important; +} + +.staffPhoto { + font-size: 70px; + vertical-align: middle; +} + +.photoBox { + .headIcon { + display: flex; + justify-content: center; + + svg { + height: 150px; + width: 140px; + opacity: .75; + } + } + :global { + .ant-upload-select-picture-card{ + height: 150px; + width: 150px; + } + } +} + +@media screen and (max-width: @screen-lg) { + .aa01tableListForm :global(.ant-form-item) { + margin-right: 24px; + } +} + +@media screen and (max-width: @screen-md) { + .aa01tableListForm :global(.ant-form-item) { + margin-right: 8px; + } +} diff --git a/src/components/BusinessBaseVip/Aa01VipTable.js b/src/components/BusinessBaseVip/Aa01VipTable.js new file mode 100644 index 0000000..916bd74 --- /dev/null +++ b/src/components/BusinessBaseVip/Aa01VipTable.js @@ -0,0 +1,579 @@ +import { PureComponent, Fragment } from 'react'; +import { DownOutlined, SearchOutlined, EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { history, connect } from 'umi'; + +import { + Card, + Button, + Dropdown, + Menu, + message, + DatePicker, + Divider, + Popconfirm, + Tooltip +} from 'antd'; + +import { initTreeSelect, userInfo } from '@/utils/globalCommon' +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import Aa01VipCreateForm from './form/Aa01VipCreateForm'; //新增表单 +import Aa01VipUpdateForm from './form/Aa01VipUpdateForm'; //修改表单 +import Aa01VipViewForm from './form/Aa01VipViewForm'; //查看表单 +import Aa01VipRenderSimpleForm from './form/Aa01VipRenderSimpleForm'; //简单查询表单 +import Aa01VipRenderAdvancedForm from './form/Aa01VipRenderAdvancedForm'; //高级查询表单 +import moment from 'moment'; + +import StandardTable from '@/components/StandardTable'; + +import style from '@/global.less'; +import styles from './index.less'; + +const { RangePicker } = DatePicker; + + +/* eslint react/no-multi-comp:0 */ +@connect(({ baseinfodata, loading }) => ({ + baseinfodata, + loading: loading.models.baseinfodata, +})) +class Aa01VipTable extends PureComponent { + + state = { + modalVisible: false, + updateModalVisible: false, + viewModalVisible: false, + expandForm: false, + selectedRows: [], + formValues: {}, + updateFormValues: {}, + viewFormValues: {}, + }; + + columns = [ + { + title: '姓名', + dataIndex: 'za0101', + key: 'za0101', + width: 80, + fixed: 'left', + }, + { + title: '性别', + dataIndex: 'aa0107', + key: 'aa0107', + render: val => { + const list = AX || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '民族', + dataIndex: 'aa0121', + key: 'aa0121', + render: val => { + const list = AE || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '出生年月', + dataIndex: 'aa0111', + key: 'aa0111', + render: val => ({moment(val).format("YYYY-MM")}) + }, + { + title: '籍贯', + dataIndex: 'aa0114', + key: 'aa0114', + render: val => { + const list = AB || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '参加工作时间', + dataIndex: 'aa0141', + key: 'aa0141', + }, + { + title: '职称', + dataIndex: 'v_aa011a', + key: 'v_aa011a', + }, + { + title: '婚姻状况', + dataIndex: 'aa0127', + key: 'aa0127', + render: val => { + const list = BG || []; + if (val) { + return ( + + + {initTreeSelect(list, val)} + + ) + } + } + }, + { + title: '健康状况', + dataIndex: 'v_aa011b', + key: 'v_aa011b', + }, + { + title: '身份证号码', + dataIndex: 'aa0177', + key: 'aa0177', + }, + { + title: '操作', + width: 220, + fixed: 'right', + render: (text, record) => ( + + this.handleViewModalVisible(true, record)}> 查看 + + this.handleUpdateModalVisible(true, record)}> 编辑 + + this.handleDeleteRecord(record)} > + 删除 + + + ), + }, + ]; + + + componentDidMount() { + const { dispatch } = this.props; + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + }); + } + + + + handleStandardTableChange = (pagination, filtersArg, sorter) => { + + const { dispatch } = this.props; + const { formValues } = this.state; + + const params = { + currentPage: pagination.current, + pageSize: pagination.pageSize, + ...formValues, + }; + + if (sorter.field) { + params.sorter = `${sorter.field}_${sorter.order}`; + } + + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: params, + }); + + + }; + + previewItem = record => { + const id = record.id; + //history.push(`/baseinfodata/basic/${id}{id}`); + }; + + handleFormReset = () => { + const { dispatch } = this.props; + this.setState({ + formValues: {}, + }); + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: {}, + }); + + }; + + toggleForm = () => { + + const { expandForm } = this.state; + this.setState({ + expandForm: !expandForm, + }); + }; + + + + handleSelectRows = rows => { + this.setState({ + selectedRows: rows, + }); + }; + + handleSearch = values => { + const { dispatch } = this.props; + this.setState({ + formValues: values, + }); + + dispatch({ + type: 'baseinfodata/query_page_for_aa01', + payload: values, + }); + + }; + + + + + handleModalVisible = flag => { + this.setState({ + modalVisible: !!flag, + }); + }; + + handleUpdateModalVisible = (flag, record) => { + this.setState({ + updateModalVisible: !!flag, + updateFormValues: record || {}, + }); + }; + + handleViewModalVisible = (flag, record) => { + + this.setState({ + viewModalVisible: !!flag, + viewFormValues: record || {}, + }); + + }; + + handleAdd = (fields, form, loading) => { + + const { dispatch } = this.props; + const params = { + za0101: fields.za0101, // 姓 名 + aa0107: fields.aa0107, // 性 别 aa0107 + aa0121: fields.aa0121, // 民 族 aa0121 + aa0111: fields.aa0111, // 出 生 年 月 aa0111 + aa0114: fields.aa0114, // 籍 贯 aa0114 + aa0117: fields.aa0117, // 出生地 aa0117 + aa0141: fields.aa0141, // 参加工作时间 aa0141 + v_aa011n: fields.v_aa011n, // 党派交叉情况 v_aa011n + v_aa011a: fields.v_aa011a, // 职 称 v_aa011a + aa0127: fields.aa0127, // 婚 姻 状 况 aa0127 + v_aa011b: fields.v_aa011b, // 健 康 状 况 v_aa011b + aa0177: fields.aa0177, // 身份证号 码 aa0177 + v_aa011c: fields.v_aa011c, // 工作单位 v_aa011c + v_aa011d: fields.v_aa011d, // 职 务 v_aa011d + v_aa011h: fields.v_aa011h, // 职务层次 v_aa011h + v_aa011f: fields.v_aa011f, // 任现职务层次时间 v_aa011f + v_aa011e: fields.v_aa011e, // 职 级 v_aa011e + v_aa011g: fields.v_aa011g, // 任 现 职 级 时 间 v_aa011g + v_aa011i: fields.v_aa011i, // 单位地址 v_aa011i + v_aa011j: fields.v_aa011j, // 固定电话 v_aa011j + v_aa011k: fields.v_aa011k, // 家庭地址 v_aa011k + ak010g: fields.ak010g, // 邮政编码 ak010g + ak010b: fields.ak010b, // 手机号码 ak010b + v_aa011l: fields.v_aa011l, // 电子邮箱 v_aa011l + v_aa011m: fields.v_aa011m, // 专业专长 v_aa011m + // za9998: fields.za9998, // 头像 + } + if(fields.file) { + const formData = new FormData(); + formData.append('file', fields.file); + dispatch({ + type: 'baseinfodata/upload_single_file', + payload: formData, + callback: ({ success, datarecord}) => { + loading(false); + if (success == true) { + params.za9998 = datarecord.id + dispatch({ + type: 'baseinfodata/insert_for_aa01', + payload: params, + callback: (res) => { + loading(false); + if (res.success == true) { + message.success('添加成功'); + form.resetFields(); + this.handleModalVisible(); + } + } + }); + } + else { + message.error('照片上传失败'); + } + } + }); + } else { + dispatch({ + type: 'baseinfodata/insert_for_aa01', + payload: params, + callback: (res) => { + loading(false); + if (res.success == true) { + message.success('添加成功'); + form.resetFields(); + this.handleModalVisible(); + } + } + }); + } + + }; + + + handleDeleteRecord = record => { + + const { dispatch } = this.props; + + dispatch({ + type: 'baseinfodata/delete_by_primarykey_for_aa01', + payload: { + recordid: record.id, + }, + callback: () => { + this.setState({ + selectedRows: [], + }); + }, + }); + + message.success('删除成功'); + + + }; + + handleUpdate = (fields, form, loading) => { + + const { dispatch } = this.props; + const params = { + id: fields.id, + za0101: fields.za0101, // 姓 名 + aa0107: fields.aa0107, // 性 别 aa0107 + aa0121: fields.aa0121, // 民 族 aa0121 + aa0111: fields.aa0111, // 出 生 年 月 aa0111 + aa0114: fields.aa0114, // 籍 贯 aa0114 + aa0117: fields.aa0117, // 出生地 aa0117 + aa0141: fields.aa0141, // 参加工作时间 aa0141 + v_aa011n: fields.v_aa011n, // 党派交叉情况 v_aa011n + v_aa011a: fields.v_aa011a, // 职 称 v_aa011a + aa0127: fields.aa0127, // 婚 姻 状 况 aa0127 + v_aa011b: fields.v_aa011b, // 健 康 状 况 v_aa011b + aa0177: fields.aa0177, // 身份证号 码 aa0177 + v_aa011c: fields.v_aa011c, // 工作单位 v_aa011c + v_aa011d: fields.v_aa011d, // 职 务 v_aa011d + v_aa011h: fields.v_aa011h, // 职务层次 v_aa011h + v_aa011f: fields.v_aa011f, // 任现职务层次时间 v_aa011f + v_aa011e: fields.v_aa011e, // 职 级 v_aa011e + v_aa011g: fields.v_aa011g, // 任 现 职 级 时 间 v_aa011g + v_aa011i: fields.v_aa011i, // 单位地址 v_aa011i + v_aa011j: fields.v_aa011j, // 固定电话 v_aa011j + v_aa011k: fields.v_aa011k, // 家庭地址 v_aa011k + ak010g: fields.ak010g, // 邮政编码 ak010g + ak010b: fields.ak010b, // 手机号码 ak010b + v_aa011l: fields.v_aa011l, // 电子邮箱 v_aa011l + v_aa011m: fields.v_aa011m, // 专业专长 v_aa011m + // za9998: fields.za9998, // 头像 + } + if(fields.file) { + const formData = new FormData(); + formData.append('file', fields.file); + dispatch({ + type: 'baseinfodata/upload_single_file', + payload: formData, + callback: ({ success, datarecord}) => { + loading(false); + if (success == true) { + params.za9998 = datarecord.id + dispatch({ + type: 'baseinfodata/update_for_aa01', + payload: params, + callback: (res) => { + loading(false); + if (res.success == true) { + message.success('修改成功'); + form.resetFields(); + this.handleUpdateModalVisible(); + } + } + }); + } + else { + message.error('照片上传失败'); + } + } + }); + } else { + dispatch({ + type: 'baseinfodata/update_for_aa01', + payload: params, + callback: (res) => { + loading(false); + if (res.success == true) { + message.success('修改成功'); + form.resetFields(); + this.handleUpdateModalVisible(); + } + } + }); + } + + }; + + renderSimpleForm() { + + const parentMethods = { + handleSearch: this.handleSearch, + handleFormReset: this.handleFormReset, + toggleForm: this.toggleForm, + submitButtons: styles.submitButtons, + }; + + return ( + + + + ); + + } + + + + renderAdvancedForm() { + + const parentMethods = { + handleSearch: this.handleSearch, + handleFormReset: this.handleFormReset, + toggleForm: this.toggleForm, + }; + + return ( + + + ); + + } + + renderForm() { + const { expandForm } = this.state; + return expandForm ? this.renderAdvancedForm() : this.renderSimpleForm(); + } + + render() { + + const { + baseinfodata: { data }, + loading, + } = this.props; + + + const { selectedRows, modalVisible, updateModalVisible, viewModalVisible, updateFormValues, viewFormValues } = this.state; + const menu = ( + + 删除 + 批量审批 + + ); + const parentMethods = { + handleAdd: this.handleAdd, + handleModalVisible: this.handleModalVisible, + }; + const updateMethods = { + handleUpdateModalVisible: this.handleUpdateModalVisible, + handleUpdate: this.handleUpdate, + }; + const viewMethods = { + handleViewModalVisible: this.handleViewModalVisible + }; + return ( + + <> + +
    + +
    {this.renderForm()}
    + + +
    + + + {selectedRows.length > 0 && ( + + + + + + + )} +
    + + +
    +
    + + + + + {updateFormValues && Object.keys(updateFormValues).length ? ( + + ) : null} + + + {viewFormValues && Object.keys(viewFormValues).length ? ( + + ) : null} + + + + ); + } +} + + +export default Aa01VipTable; + diff --git a/src/components/BusinessBaseVip/form/Aa01VipCreateForm.js b/src/components/BusinessBaseVip/form/Aa01VipCreateForm.js new file mode 100644 index 0000000..9444448 --- /dev/null +++ b/src/components/BusinessBaseVip/form/Aa01VipCreateForm.js @@ -0,0 +1,366 @@ +import React, { useState } from 'react'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { + Form, + Input, + DatePicker, + Modal, + Button, + Upload +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { MyIcon } from "@/components/Icon" +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import moment from 'moment'; +import styles from '@/global.less'; +import style from '../index.less'; + +const FormItem = Form.Item; + +const Aa01CreateForm = (props) => { + const [form] = Form.useForm(); + const { modalVisible, handleAdd, handleModalVisible, dictDataList } = props; + const [error, setError] = useState([]); + const [subLoading, setSubLoading] = useState(false); + const [imageUrl, setImageUrl] = useState(''); + const [imageFile, setImageFile] = useState(null); + const okHandle = () => { + // za9998 头像 + form + .validateFields() + .then(fieldsValue => { + console.log(fieldsValue, 'fieldsValue'); + setSubLoading(true); + setError([]); + fieldsValue.aa0111 = fieldsValue.aa0111 ? moment(fieldsValue.aa0111).format("YYYY-MM-DD") : null; + fieldsValue.aa0141 = fieldsValue.aa0141 ? moment(fieldsValue.aa0141).format("YYYY-MM-DD") : null; + fieldsValue.v_aa011f = fieldsValue.v_aa011f ? moment(fieldsValue.v_aa011f).format("YYYY-MM-DD") : null; + fieldsValue.v_aa011g = fieldsValue.v_aa011g ? moment(fieldsValue.v_aa011g).format("YYYY-MM-DD") : null; + fieldsValue.file = imageFile; + handleAdd(fieldsValue, form, setSubLoading); + }) + .catch(errInfo => { + onFinishFailed(errInfo) + }); + + }; + + const onFinishFailed = (errorInfo) => { + setError(errorInfo.errorFields) + } + + const getErrorInfo = (errors) => { + const errorCount = errors.filter((item) => item.errors.length > 0).length; + if (!errors || errorCount === 0) { + return null; + } + + return ( + + {errorCount} + + ); + }; + + const beforeUpload = file => { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg'; + if (!isJpgOrPng) { + message.error('只能上传 JPG/JPEG/PNG 格式的文件!'); + } + const isLt2M = file.size / 1024 / 1024 < 2; + if (!isLt2M) { + message.error('图像必须小于2MB!'); + } + return isJpgOrPng && isLt2M; + } + + const handleChange = info => { + if (info.file.status === 'done') { + // Get this url from response in real world. + if (window.FileReader) { + const reader = new FileReader(); + reader.readAsDataURL(info.file.originFileObj); + reader.addEventListener('load', () => { + setImageUrl(reader.result); + }); + } + setImageFile(info.file.originFileObj); + } + }; + + const uploadButton = (); + + return ( + handleModalVisible()} + afterClose={() => handleModalVisible()} + footer={<>{getErrorInfo(error)} + + } + > +
    + + + + + + + + + + + + + + {/* 照片(za9998) */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    人员基本情况信息
    + 姓名 + + + + + + 性别 + + + + + + 民族 + + + + + +
    + + {imageUrl ? 图片加载失败 : uploadButton} + +
    上传照片
    +
    +
    + 出生年月 + + + + + + 籍贯 + + + + + + 出生地 + + + + +
    + 参加工作时间 + + + + + + 党派交叉情况 + + + + + + 职称 + + + + +
    + 婚姻状况 + + + + + + 健康状况 + + + + + + 身份证号码 + + + + +
    + 工作单位 + + + + + + 职务 + + + + +
    + 职务层次 + + + + + + 任现职务层次时间 + + + + +
    + 职级 + + + + + + 任现职级时间 + + + + +
    + 单位地址 + + + + + + 固定电话 + + + + +
    + 家庭地址 + + + + + + 邮政编码 + + + + +
    + 手机号码 + + + + + + 电子邮箱 + + + + +
    + 专业专长 + + + + +
    +
    +
    + ); +}; + +export default Aa01CreateForm; + diff --git a/src/components/BusinessBaseVip/form/Aa01VipRenderAdvancedForm.js b/src/components/BusinessBaseVip/form/Aa01VipRenderAdvancedForm.js new file mode 100644 index 0000000..14cfe8e --- /dev/null +++ b/src/components/BusinessBaseVip/form/Aa01VipRenderAdvancedForm.js @@ -0,0 +1,99 @@ +import { + Row, + Col, + Form, + Input, + DatePicker, + Button, +} from 'antd'; +import { UpOutlined } from '@ant-design/icons'; +import style from "@/global.less"; +const FormItem = Form.Item; + +const Aa01RenderAdvancedForm = (props) => { + + const [form] = Form.useForm(); + + const { handleSearch, handleFormReset, toggleForm } = props; + + const onFinish = values => { + //console.log('Received values of form: ', values); + handleSearch(values); + }; + + const myhandleFormReset = () => { + form.resetFields(); + handleFormReset(); + }; + + return ( + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + 收起 + +
    + + +
    + +
    + + ); + + +}; + +export default Aa01RenderAdvancedForm; + + + diff --git a/src/components/BusinessBaseVip/form/Aa01VipRenderSimpleForm.js b/src/components/BusinessBaseVip/form/Aa01VipRenderSimpleForm.js new file mode 100644 index 0000000..21ef9ed --- /dev/null +++ b/src/components/BusinessBaseVip/form/Aa01VipRenderSimpleForm.js @@ -0,0 +1,70 @@ +import { + Row, + Col, + Form, + Input, + DatePicker, + Button, +} from 'antd'; +import { DownOutlined } from '@ant-design/icons'; +import style from "@/global.less"; +const FormItem = Form.Item; + +const Aa01RenderSimpleForm = (props) => { + + const [form] = Form.useForm(); + + const { submitButtons, handleSearch, handleFormReset, toggleForm } = props; + + const onFinish = values => { + //console.log('Received values of form: ', values); + handleSearch(values); + }; + + const myhandleFormReset = () => { + form.resetFields(); + handleFormReset(); + }; + + return ( + +
    + + + + + + + + + + + + + + + + + +
    + + + + 展开 + +
    + +
    + +
    + + ); + + +}; + +export default Aa01RenderSimpleForm; \ No newline at end of file diff --git a/src/components/BusinessBaseVip/form/Aa01VipUpdateForm.js b/src/components/BusinessBaseVip/form/Aa01VipUpdateForm.js new file mode 100644 index 0000000..8920b84 --- /dev/null +++ b/src/components/BusinessBaseVip/form/Aa01VipUpdateForm.js @@ -0,0 +1,401 @@ +import React, { useState } from 'react'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { + Form, + Input, + DatePicker, + Modal, + Button, + Upload +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { MyIcon } from "@/components/Icon" +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import moment from 'moment'; +import styles from '@/global.less'; +import style from '../index.less'; + +const FormItem = Form.Item; + +const Aa01UpdateForm = (props) => { + + const { handleUpdate, updateModalVisible, handleUpdateModalVisible, values, dictDataList } = props; + const [form] = Form.useForm(); + const [error, setError] = useState([]); + const [subLoading, setSubLoading] = useState(false); + const [imageUrl, setImageUrl] = useState(''); + const [imageFile, setImageFile] = useState(null); + React.useEffect(() => { + if(!!values.zp){ + setImageUrl(values.zp.fileurl) + } + form?.setFieldsValue({ + za0101: values.za0101, // 姓 名 za0101 + aa0107: values.aa0107, // 性 别 aa0107 + aa0121: values.aa0121, // 民 族 aa0121 + aa0111: values.aa0111 ? moment(values.aa0111, "YYYY-MM") : null, // 出 生 年 月 aa0111 + aa0114: values.aa0114, // 籍 贯 aa0114 + aa0117: values.aa0117, // 出生地 aa0117 + aa0141: values.aa0141 ? moment(values.aa0141, "YYYY-MM-DD") : null, // 参加工作时间 aa0141 + v_aa011n: values.v_aa011n, // 党派交叉情况 v_aa011n + v_aa011a: values.v_aa011a, // 职 称 v_aa011a + aa0127: values.aa0127, // 婚 姻 状 况 aa0127 + v_aa011b: values.v_aa011b, // 健 康 状 况 v_aa011b + aa0177: values.aa0177, // 身份证号 码 aa0177 + v_aa011c: values.v_aa011c, // 工作单位 v_aa011c + v_aa011d: values.v_aa011d, // 职 务 v_aa011d + v_aa011h: values.v_aa011h, // 职务层次 v_aa011h + v_aa011f: values.v_aa011f ? moment(values.v_aa011f, "YYYY-MM-DD") : null, // 任现职务层次时间 v_aa011f + v_aa011e: values.v_aa011e, // 职 级 v_aa011e + v_aa011g: values.v_aa011g ? moment(values.v_aa011g, "YYYY-MM-DD") : null, // 任 现 职 级 时 间 v_aa011g + v_aa011i: values.v_aa011i, // 单位地址 v_aa011i + v_aa011j: values.v_aa011j, // 固定电话 v_aa011j + v_aa011k: values.v_aa011k, // 家庭地址 v_aa011k + ak010g: values.ak010g, // 邮政编码 ak010g + ak010b: values.ak010b, // 手机号码 ak010b + v_aa011l: values.v_aa011l, // 电子邮箱 v_aa011l + v_aa011m: values.v_aa011m, // 专业专长 v_aa011m + za9998: values.za9998, // 头像 + }); + }, [values, form]); + + const handleLocalUpdate = () => { + + form + .validateFields() + .then(fieldsValue => { + setSubLoading(true); + setError([]); + fieldsValue.id = values?.id; + fieldsValue.aa0111 = fieldsValue.aa0111 ? moment(fieldsValue.aa0111).format("YYYY-MM-DD") : null; + fieldsValue.aa0141 = fieldsValue.aa0141 ? moment(fieldsValue.aa0141).format("YYYY-MM-DD") : null; + fieldsValue.v_aa011f = fieldsValue.v_aa011f ? moment(fieldsValue.v_aa011f).format("YYYY-MM-DD") : null; + fieldsValue.v_aa011g = fieldsValue.v_aa011g ? moment(fieldsValue.v_aa011g).format("YYYY-MM-DD") : null; + fieldsValue.file = imageFile; + handleUpdate(fieldsValue, form, setSubLoading); + }) + .catch(errInfo => { + onFinishFailed(errInfo) + }); + + }; + + const onFinishFailed = (errorInfo) => { + setError(errorInfo.errorFields) + } + + const getErrorInfo = (errors) => { + const errorCount = errors.filter((item) => item.errors.length > 0).length; + if (!errors || errorCount === 0) { + return null; + } + + return ( + + {errorCount} + + ); + }; + const beforeUpload = file => { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg'; + if (!isJpgOrPng) { + message.error('只能上传 JPG/JPEG/PNG 格式的文件!'); + } + const isLt2M = file.size / 1024 / 1024 < 2; + if (!isLt2M) { + message.error('图像必须小于2MB!'); + } + return isJpgOrPng && isLt2M; + } + + const handleChange = info => { + if (info.file.status === 'done') { + // Get this url from response in real world. + if (window.FileReader) { + const reader = new FileReader(); + reader.readAsDataURL(info.file.originFileObj); + reader.addEventListener('load', () => { + setImageUrl(reader.result); + }); + } + setImageFile(info.file.originFileObj); + } + }; + + const uploadButton = (); + + return ( + handleLocalUpdate()} + onCancel={() => handleUpdateModalVisible()} + footer={<>{getErrorInfo(error)} + + } + > +
    + + + + + + + + + + + + + + {/* 照片(za9998) */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    人员基本情况信息
    + 姓名 + + + + + + 性别 + + + + + + 民族 + + + + + +
    + + {imageUrl ? 图片加载失败 : uploadButton} + +
    上传照片
    +
    +
    + 出生年月 + + + + + + 籍贯 + + + + + + 出生地 + + + + +
    + 参加工作时间 + + + + + + 党派交叉情况 + + + + + + 职称 + + + + +
    + 婚姻状况 + + + + + + 健康状况 + + + + + + 身份证号码 + + + + +
    + 工作单位 + + + + + + 职务 + + + + +
    + 职务层次 + + + + + + 任现职务层次时间 + + + + +
    + 职级 + + + + + + 任现职级时间 + + + + +
    + 单位地址 + + + + + + 固定电话 + + + + +
    + 家庭地址 + + + + + + 邮政编码 + + + + +
    + 手机号码 + + + + + + 电子邮箱 + + + + +
    + 专业专长 + + + + +
    +
    + +
    + ); + +} + +export default Aa01UpdateForm; diff --git a/src/components/BusinessBaseVip/form/Aa01VipViewForm.js b/src/components/BusinessBaseVip/form/Aa01VipViewForm.js new file mode 100644 index 0000000..0e51924 --- /dev/null +++ b/src/components/BusinessBaseVip/form/Aa01VipViewForm.js @@ -0,0 +1,317 @@ +import { + Form, + Input, + DatePicker, + Modal, + Upload +} from 'antd'; +import SelectOptionTree from '@/components/SelectOptionTree'; +import { XQ, AE, AX, _09, AB, OR, BG, AT, HC, OC, OD, RF, X4 } from '@/utils/pr_new_datadictionary'; +import userAvatar from '@/assets/img/user_avatar.png'; +import styles from "@/global.less"; +import moment from 'moment'; +import style from '../index.less'; +import React, { useState } from 'react'; +const FormItem = Form.Item; + +const Aa01ViewForm = (props) => { + + const { viewModalVisible, handleViewModalVisible, values } = props; + + const [form] = Form.useForm(); + const [photoUrl, setPhotoUrl] = useState(); + React.useEffect(() => { + if(!!values.zp){ + setPhotoUrl(values.zp.fileurl) + } + form?.setFieldsValue({ + za0101: values.za0101, // 姓 名 za0101 + aa0107: values.aa0107, // 性 别 aa0107 + aa0121: values.aa0121, // 民 族 aa0121 + aa0111: values.aa0111 ? moment(values.aa0111, "YYYY-MM") : null, // 出 生 年 月 aa0111 + aa0114: values.aa0114, // 籍 贯 aa0114 + aa0117: values.aa0117, // 出生地 aa0117 + aa0141: values.aa0141 ? moment(values.aa0141, "YYYY-MM-DD") : null, // 参加工作时间 aa0141 + v_aa011n: values.v_aa011n, // 党派交叉情况 v_aa011n + v_aa011a: values.v_aa011a, // 职 称 v_aa011a + aa0127: values.aa0127, // 婚 姻 状 况 aa0127 + v_aa011b: values.v_aa011b, // 健 康 状 况 v_aa011b + aa0177: values.aa0177, // 身份证号 码 aa0177 + v_aa011c: values.v_aa011c, // 工作单位 v_aa011c + v_aa011d: values.v_aa011d, // 职 务 v_aa011d + v_aa011h: values.v_aa011h, // 职务层次 v_aa011h + v_aa011f: values.v_aa011f ? moment(values.v_aa011f, "YYYY-MM-DD") : null, // 任现职务层次时间 v_aa011f + v_aa011e: values.v_aa011e, // 职 级 v_aa011e + v_aa011g: values.v_aa011g ? moment(values.v_aa011g, "YYYY-MM-DD") : null, // 任 现 职 级 时 间 v_aa011g + v_aa011i: values.v_aa011i, // 单位地址 v_aa011i + v_aa011j: values.v_aa011j, // 固定电话 v_aa011j + v_aa011k: values.v_aa011k, // 家庭地址 v_aa011k + ak010g: values.ak010g, // 邮政编码 ak010g + ak010b: values.ak010b, // 手机号码 ak010b + v_aa011l: values.v_aa011l, // 电子邮箱 v_aa011l + v_aa011m: values.v_aa011m, // 专业专长 v_aa011m + za9998: values.za9998, // 头像 + }); + }, [values, form]); + + + return ( + handleViewModalVisible(false, values)} + onCancel={() => handleViewModalVisible(false, values)} + afterClose={() => handleViewModalVisible()} + > + +
    + + + + + + + + + + + + + {/* 照片(za9998) */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    人员基本情况信息
    + 姓名 + + + + + + 性别 + + + + + + 民族 + + + + + +
    + 图片加载失败 +
    +
    + 出生年月 + + + + + + 籍贯 + + + + + + 出生地 + + + + +
    + 参加工作时间 + + + + + + 党派交叉情况 + + + + + + 职称 + + + + +
    + 婚姻状况 + + + + + + 健康状况 + + + + + + 身份证号码 + + + + +
    + 工作单位 + + + + + + 职务 + + + + +
    + 职务层次 + + + + + + 任现职务层次时间 + + + + +
    + 职级 + + + + + + 任现职级时间 + + + + +
    + 单位地址 + + + + + + 固定电话 + + + + +
    + 家庭地址 + + + + + + 邮政编码 + + + + +
    + 手机号码 + + + + + + 电子邮箱 + + + + +
    + 专业专长 + + + + +
    +
    + +
    + ); + +} + +export default Aa01ViewForm; \ No newline at end of file diff --git a/src/components/BusinessBaseVip/index.less b/src/components/BusinessBaseVip/index.less new file mode 100644 index 0000000..f77fe23 --- /dev/null +++ b/src/components/BusinessBaseVip/index.less @@ -0,0 +1,83 @@ +.aa01tableList { + .aa01tableListOperator { + margin-bottom: 16px; + button { + margin-right: 8px; + } + } +} + +.aa01tableListForm { + :global { + .ant-form-item { + display: flex; + margin-right: 0; + margin-bottom: 24px; + > .ant-form-item-label { + width: auto; + padding-right: 8px; + line-height: 32px; + } + .ant-form-item-control { + line-height: 32px; + } + } + .ant-form-item-control-wrapper { + flex: 1; + } + } + .submitButtons { + display: block; + margin-bottom: 24px; + white-space: nowrap; + } +} + +.icon { + width: 0.8em; + height: 0.8em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +.ant-upload{ + width: 130px !important; + height: 140px !important; +} + +.staffPhoto { + font-size: 70px; + vertical-align: middle; +} + +.photoBox { + .headIcon { + display: flex; + justify-content: center; + + svg { + height: 150px; + width: 140px; + opacity: .75; + } + } + :global { + .ant-upload-select-picture-card{ + height: 150px; + width: 150px; + } + } +} + +@media screen and (max-width: @screen-lg) { + .aa01tableListForm :global(.ant-form-item) { + margin-right: 24px; + } +} + +@media screen and (max-width: @screen-md) { + .aa01tableListForm :global(.ant-form-item) { + margin-right: 8px; + } +} diff --git a/src/components/GlobalComponent/Breadcrumb.jsx b/src/components/GlobalComponent/Breadcrumb.jsx new file mode 100644 index 0000000..07d8e21 --- /dev/null +++ b/src/components/GlobalComponent/Breadcrumb.jsx @@ -0,0 +1,275 @@ +import React, { useState, useEffect } from 'react'; +import { Breadcrumb } from 'antd'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { history, useLocation } from '@umijs/max'; +import './breadcrumb.less'; + +const CustomBreadcrumb = () => { + // 从sessionStorage初始化面包屑数据,与Vue实例保持一致 + const [breadcrumbList, setBreadcrumbList] = useState(() => { + return JSON.parse(sessionStorage.getItem('breadcrumb')) || []; + }); + + const [currentRouterName, setCurrentRouterName] = useState(''); + + const location = useLocation(); + + // 递归搜索window.dynamicRoute中匹配当前路径的路由项 + const findMatchedRoute = (routes, pathname) => { + for (const route of routes) { + // 检查当前路由项是否匹配 + if (route.key === pathname) { + return route; + } + // 如果有子路由,递归搜索 + if (route.children && route.children.length > 0) { + const matchedChild = findMatchedRoute(route.children, pathname); + if (matchedChild) { + return matchedChild; + } + } + } + return null; + }; + + // 获取当前路由的真实信息 + const getCurrentRoute = () => { + const pathname = location.pathname || '/'; + const href = window.location.href; + + console.log('当前路径:', pathname); + + // 使用自定义函数在window.dynamicRoute中匹配当前路径 + let matchedRoute = null; + if (window.dynamicRoute && Array.isArray(window.dynamicRoute)) { + matchedRoute = findMatchedRoute(window.dynamicRoute, pathname); + } + + console.log('匹配的路由:', matchedRoute); + + // 如果有匹配的路由,获取路由信息 + if (matchedRoute) { + // 使用route.label作为标题,route.key作为路径标识符 + const routeTitle = matchedRoute.label || '未知页面'; + const routeKey = matchedRoute.key || pathname; + + return { + name: routeKey.replace(/\//g, '_').slice(1) || 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: routeTitle, + affix: pathname === '/' + } + }; + } + + // 如果没有从window.dynamicRoute中匹配到路由,使用原来的逻辑处理 + // 将路径按照/分割,获取最后一段作为名称 + const parts = pathname.split('/').filter(Boolean); + + // 根路径处理 + if (parts.length === 0) { + return { + name: 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: '首页', + affix: true + } + }; + } + + // 获取最后一段作为页面名称 + const lastPart = parts[parts.length - 1]; + + // 使用最后一段作为标题 + const routeTitle = lastPart || '未知页面'; + + return { + name: lastPart.replace(/\//g, '_') || 'home', + path: pathname, + href, + fullPath: href, + meta: { + title: routeTitle, + affix: pathname === '/' + } + }; + }; + + // 生成面包屑数据 - 修复了path指向错误的问题 + const generateBreadcrumb = () => { + // 从当前路由获取信息 + const currentRoute = getCurrentRoute(); + const { meta: { title }, name, path } = currentRoute; + + // 如果title存在,将路由信息添加到breadcrumbList数组 + if (title) { + const newBreadcrumbList = [...breadcrumbList]; + newBreadcrumbList.push({ + name, + path: path, // 正确使用当前路由的path属性 + title + }); + + // 使用reduce方法对breadcrumbList进行去重处理,并过滤 business 页签 + const uniqueList = newBreadcrumbList.reduce((accumulator, current) => { + // 过滤掉 business 页签(title 为 business 或 key 仅为 business) + if ( + current.title === 'business' || + current.name === 'topnavbar00_business' || + current.path === '/topnavbar00/business' + ) { + return accumulator; + } + const x = accumulator.find(item => item.name === current.name); + if (!x) { + return accumulator.concat(current); + } else { + accumulator.forEach((val, i) => { + if (val.name == current.name) { + val.title = current.title; + val.path = current.path; + } + }); + return accumulator; + } + }, []); + + // 将处理后的面包屑数据存储到sessionStorage中 + setBreadcrumbList(uniqueList); + sessionStorage.setItem('breadcrumb', JSON.stringify(uniqueList)); + } + }; + + // 处理导航逻辑 + const toPath = (item) => { + const currentRoute = getCurrentRoute(); + // 如果路径相同,点击导航则不跳转 + if (item.name === currentRoute.name) { + return ''; + } + // 如果是固定路由,则不跳转 + if (item.meta?.affix) { + return ''; + } else { + // 跳转路由 + return item.path; + } + }; + + // 关闭面包屑项,先跳转再删除面包屑 + const cloneCurrentPage = (item, index) => { + // 如果没有面包屑页面或只剩一个页面,跳转到根目录 + if (!breadcrumbList || breadcrumbList.length <= 1) { + // 使用window.location.href进行有感刷新 + window.location.href = '/'; + return; + } + + const updatedList = [...breadcrumbList]; + updatedList.splice(index, 1); + + let length = updatedList.length; + // 先进行页面跳转 + if (currentRouterName === item.name && length > 0) { + // 使用history进行无刷新跳转 + history.push(updatedList[length - 1].path); + } + + // 然后删除面包屑项 + setBreadcrumbList(updatedList); + sessionStorage.setItem('breadcrumb', JSON.stringify(updatedList)); + }; + + // 处理面包屑项点击事件 + const handleItemClick = (item) => { + const path = toPath(item); + if (path) { + console.log(currentRouterName, 'yp') + // 使用history进行无刷新跳转 + history.push(path); + } + }; + + // 组件挂载时初始化 + useEffect(() => { + const currentRoute = getCurrentRoute(); + setCurrentRouterName(currentRoute.name); + generateBreadcrumb(); + }, []); + + // 监听路由变化,当路由变化时触发generateBreadcrumb方法 + useEffect(() => { + const handleRouteChange = () => { + const currentRoute = getCurrentRoute(); + setCurrentRouterName(currentRoute.name); + generateBreadcrumb(); + }; + + // 监听浏览器前进后退事件 + window.addEventListener('popstate', handleRouteChange); + + // 重写pushState和replaceState方法以捕获更多路由变化 + const originalPushState = window.history.pushState; + const originalReplaceState = window.history.replaceState; + + window.history.pushState = function (...args) { + originalPushState.apply(this, args); + handleRouteChange(); + }; + + window.history.replaceState = function (...args) { + originalReplaceState.apply(this, args); + handleRouteChange(); + }; + + // 监听hash变化(针对hash路由) + window.addEventListener('hashchange', handleRouteChange); + + return () => { + window.removeEventListener('popstate', handleRouteChange); + window.removeEventListener('hashchange', handleRouteChange); + // 还原原始方法 + window.history.pushState = originalPushState; + window.history.replaceState = originalReplaceState; + }; + }, [breadcrumbList, location]); + + // 当location.pathname变化时,自动更新currentRouterName + useEffect(() => { + const currentRoute = getCurrentRoute(); + setCurrentRouterName(currentRoute.name); + }, [location.pathname]); + + return ( +
    + + {breadcrumbList.map((item, index) => ( + handleItemClick(item)} + className={`breadcrumb-item ${item.name === currentRouterName ? 'breadcrumb-item-active' : ''}`} + > + + {item.title} + { + e.stopPropagation(); + cloneCurrentPage(item, index); + }} + /> + + + ))} + +
    + ); +}; + +export default CustomBreadcrumb; \ No newline at end of file diff --git a/src/components/GlobalComponent/breadcrumb.less b/src/components/GlobalComponent/breadcrumb.less new file mode 100644 index 0000000..5695a96 --- /dev/null +++ b/src/components/GlobalComponent/breadcrumb.less @@ -0,0 +1,164 @@ +// 面包屑容器样式 +.bread-crumb { + // display: flex; + align-items: center; + overflow-x: auto; + overflow-y: hidden; + height: auto; + max-width: 100%; + margin: 8px 0; + padding: 0 12px; + background-color: transparent; + flex-wrap: nowrap; + justify-content: flex-start; + box-sizing: border-box; +} + +// 面包屑容器滚动条样式 +.bread-crumb::-webkit-scrollbar { + height: 4px; +} + +.bread-crumb::-webkit-scrollbar-track { + background-color: #f1f1f1; + border-radius: 2px; +} + +.bread-crumb::-webkit-scrollbar-thumb { + background-color: #c1c1c1; + border-radius: 2px; +} + +.bread-crumb::-webkit-scrollbar-thumb:hover { + background-color: #a1a1a1; +} + +// 面包屑导航容器 +.ant-breadcrumb { + display: flex !important; + flex-wrap: nowrap !important; + align-items: center; + gap: 8px; + margin: 0; + padding: 0; +} + +// 面包屑项基础样式 +.breadcrumb-item { + height: 32px; + padding: 0 12px; + border-radius: 8px; + // 使用rgba格式设置带透明度的背景色,最后一个参数0.13表示13%的透明度 + background-color: rgba(186, 186, 186, 0.13); + // border: 1px solid #E8E8E8; + margin-right: 8px; + display: flex; + align-items: center; + transition: all 0.3s ease; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04); + box-sizing: border-box; + + // 移除默认的分隔符 + .ant-breadcrumb-separator { + display: none !important; + } + + // 链接样式 + .ant-breadcrumb-link { + display: flex; + align-items: center; + height: 100%; + color: #808080 !important; + background: transparent !important; + padding: 0; + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + } + + // 悬停效果 + &:hover { + border-color: #0056FF; + // transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 86, 255, 0.1); + } +} + +// 面包屑项选中状态样式 +.breadcrumb-item-active { + background-color: rgba(94, 121, 246, 0.13); + // border-color: #0056FF; + + .ant-breadcrumb-link { + color: #0056FF !important; + font-weight: 500; + } +} + +// 面包屑文本选中状态样式 - 对应第252行代码中的条件样式 +.breadcrumb-item-text-active { + color: #0056FF !important; + font-weight: 500; +} + +// 面包屑项内容容器 +.breadcrumb-item-content { + display: flex; + align-items: center; + height: 100%; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + gap: 20px; +} + +// 面包屑项文本样式 +.breadcrumb-item-text { + font-weight: 500; + font-size: 16px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; + + .breadcrumb-item-text-active { + color: #0056FF !important; + } +} + +// 关闭图标样式 +.breadcrumb-close-icon { + font-size: 14px; + // color: #7D9CD8; + // margin-left: 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: color 0.3s ease; + flex-shrink: 0; + + &:hover { + color: #0056FF; + } +} + +// 确保所有面包屑链接样式一致 +.ant-breadcrumb-link { + color: #808080 !important; + transition: color 0.3s ease; +} + +.ant-breadcrumb-link:hover { + color: #0056FF !important; +} + +.ant-breadcrumb-item-active .ant-breadcrumb-link { + color: #0056FF !important; +} \ No newline at end of file diff --git a/src/components/GlobalComponent/index.js b/src/components/GlobalComponent/index.js new file mode 100644 index 0000000..ac2716b --- /dev/null +++ b/src/components/GlobalComponent/index.js @@ -0,0 +1,80 @@ +import React, { PureComponent } from 'react'; +import * as plugins from 'antd'; +import CustomBreadcrumb from './Breadcrumb'; + +class GlobalComponent extends PureComponent { + constructor(props) { + super(props) + this.props = props + + this.Affix = plugins.Affix + this.Alert = plugins.Alert + this.Anchor = plugins.Anchor + this.AutoComplete = plugins.AutoComplete + this.Avatar = plugins.Avatar + this.BackTop = plugins.BackTop + this.Badge = plugins.Badge + this.Breadcrumb = plugins.Breadcrumb + this.Button = plugins.Button + this.Calendar = plugins.Calendar + this.Card = plugins.Card + this.Carousel = plugins.Carousel + this.Cascader = plugins.Cascader + this.Checkbox = plugins.Checkbox + this.Col = plugins.Col + this.Collapse = plugins.Collapse + // this.Comment = plugins.Comment + this.ConfigProvider = plugins.ConfigProvider + this.Descriptions = plugins.Descriptions + this.Divider = plugins.Divider + this.Drawer = plugins.Drawer + this.Dropdown = plugins.Dropdown + this.Empty = plugins.Empty + this.Form = plugins.Form + this.Grid = plugins.Grid + this.Image = plugins.Image + this.Input = plugins.Input + this.InputNumber = plugins.InputNumber + this.Layout = plugins.Layout + this.List = plugins.List + this.Mentions = plugins.Mentions + this.Menu = plugins.Menu + this.Modal = plugins.Modal + // this.PageHeader = plugins.PageHeader + this.Pagination = plugins.Pagination + this.Popconfirm = plugins.Popconfirm + this.Popover = plugins.Popover + this.Progress = plugins.Progress + this.Radio = plugins.Radio + this.Rate = plugins.Rate + this.Result = plugins.Result + this.Row = plugins.Row + this.Segmented = plugins.Segmented + this.Select = plugins.Select + this.Skeleton = plugins.Skeleton + this.Slider = plugins.Slider + this.Space = plugins.Space + this.Spin = plugins.Spin + this.Statistic = plugins.Statistic + this.Steps = plugins.Steps + this.Switch = plugins.Switch + this.Table = plugins.Table + this.Tabs = plugins.Tabs + this.Tag = plugins.Tag + this.TimePicker = plugins.TimePicker + this.Timeline = plugins.Timeline + this.Tooltip = plugins.Tooltip + this.Transfer = plugins.Transfer + this.Tree = plugins.Tree + this.TreeSelect = plugins.TreeSelect + this.Typography = plugins.Typography + this.Upload = plugins.Upload + this.message = plugins.message + // 自定义组件 + this.CustomBreadcrumb = CustomBreadcrumb; + } +} + +export default GlobalComponent; +// 同时导出自定义面包屑组件,方便直接使用 +export { CustomBreadcrumb }; diff --git a/src/components/Icon/IconFont.js b/src/components/Icon/IconFont.js new file mode 100644 index 0000000..9e6c429 --- /dev/null +++ b/src/components/Icon/IconFont.js @@ -0,0 +1,6 @@ +import { createFromIconfontCN } from '@ant-design/icons'; +import defaultSettings from "../../../config/defaultSettings"; + +export const IconFont = createFromIconfontCN({ + scriptUrl: `//at.alicdn.com/t/font_2163129_p3ldyoksz3s.js` +}); diff --git a/src/components/Icon/MyIcon.js b/src/components/Icon/MyIcon.js new file mode 100644 index 0000000..889c4bd --- /dev/null +++ b/src/components/Icon/MyIcon.js @@ -0,0 +1,5 @@ +import { IconFont } from "@/components/Icon/IconFont"; + +export default ({type, font = 13, style, className}) => { + return +} diff --git a/src/components/Icon/TextIcon.js b/src/components/Icon/TextIcon.js new file mode 100644 index 0000000..696bffb --- /dev/null +++ b/src/components/Icon/TextIcon.js @@ -0,0 +1,12 @@ +import MyIcon from "@/components/Icon/MyIcon" + +export default ({icon, text, font = 13, style, className, onClick, back = true}) => { + return back ? + + {text} + : + + {text} + + +} diff --git a/src/components/Icon/TooltipIcon.js b/src/components/Icon/TooltipIcon.js new file mode 100644 index 0000000..8862604 --- /dev/null +++ b/src/components/Icon/TooltipIcon.js @@ -0,0 +1,7 @@ +import { Tooltip } from "antd"; + +export default ({icon, title, font, style, onClick}) => { + return + {icon} + +} diff --git a/src/components/Icon/TooltipTextIcon.js b/src/components/Icon/TooltipTextIcon.js new file mode 100644 index 0000000..de67ed7 --- /dev/null +++ b/src/components/Icon/TooltipTextIcon.js @@ -0,0 +1,7 @@ +import { Tooltip } from "antd"; + +export default ({icon, title, font, style, text, onClick}) => { + return + {icon} {text} + +} diff --git a/src/components/Icon/index.js b/src/components/Icon/index.js new file mode 100644 index 0000000..da39666 --- /dev/null +++ b/src/components/Icon/index.js @@ -0,0 +1,20 @@ +import MyIcon from './MyIcon'; +import TextIcon from './TextIcon'; +import TooltipIcon from './TooltipIcon'; +import TooltipTextIcon from './TooltipTextIcon'; + +const Icon = { + MyIcon, + TextIcon, + TooltipIcon, + TooltipTextIcon +}; + +export { + Icon as default, + MyIcon, + TextIcon, + TooltipIcon, + TooltipTextIcon +}; + diff --git a/src/components/IconModal/index.js b/src/components/IconModal/index.js new file mode 100644 index 0000000..e0546b9 --- /dev/null +++ b/src/components/IconModal/index.js @@ -0,0 +1,141 @@ +import React, { PureComponent, createElement } from 'react' +import { Modal, Tabs, Tooltip, Input} from 'antd' +import * as Icon from '@ant-design/icons' +import classNames from 'classnames' +import styles from './index.less' + +const { Search } = Input + +const webIcons = ['AccountBookOutlined','AimOutlined','AlertOutlined','ApartmentOutlined','ApiOutlined','AppstoreAddOutlined','AppstoreOutlined','AudioOutlined','AudioMutedOutlined','AuditOutlined','BankOutlined','BarcodeOutlined','BarsOutlined','BellOutlined','BlockOutlined','BookOutlined','BorderOutlined','BorderlessTableOutlined','BranchesOutlined','BugOutlined','BuildOutlined','BulbOutlined','CalculatorOutlined','CalendarOutlined','CameraOutlined','CarOutlined','CarryOutOutlined','CiCircleOutlined','CiOutlined','ClearOutlined','CloudDownloadOutlined','CloudOutlined','CloudServerOutlined','CloudSyncOutlined','CloudUploadOutlined','ClusterOutlined','CodeOutlined','CoffeeOutlined','CommentOutlined','CompassOutlined','CompressOutlined','ConsoleSqlOutlined','ContactsOutlined','ContainerOutlined','ControlOutlined','CopyrightOutlined','CreditCardOutlined','CrownOutlined','CustomerServiceOutlined','DashboardOutlined','DatabaseOutlined','DeleteColumnOutlined','DeleteRowOutlined','DeliveredProcedureOutlined','DeploymentUnitOutlined','DesktopOutlined','DingtalkOutlined','DisconnectOutlined','DislikeOutlined','DollarCircleOutlined','DollarOutlined','DownloadOutlined','EllipsisOutlined','EnvironmentOutlined','EuroCircleOutlined','EuroOutlined','ExceptionOutlined','ExpandAltOutlined','ExpandOutlined','ExperimentOutlined','ExportOutlined','EyeOutlined','EyeInvisibleOutlined','FieldBinaryOutlined','FieldNumberOutlined','FieldStringOutlined','FieldTimeOutlined','FileAddOutlined','FileDoneOutlined','FileExcelOutlined','FileExclamationOutlined','FileOutlined','FileGifOutlined','FileImageOutlined','FileJpgOutlined','FileMarkdownOutlined','FilePdfOutlined','FilePptOutlined','FileProtectOutlined','FileSearchOutlined','FileSyncOutlined','FileTextOutlined','FileUnknownOutlined','FileWordOutlined','FileZipOutlined','FilterOutlined','FireOutlined','FlagOutlined','FolderAddOutlined','FolderOutlined','FolderOpenOutlined','FolderViewOutlined','ForkOutlined','FormatPainterOutlined','FrownOutlined','FunctionOutlined','FundProjectionScreenOutlined','FundViewOutlined','FunnelPlotOutlined','GatewayOutlined','GifOutlined','GiftOutlined','GlobalOutlined','GoldOutlined','GroupOutlined','HddOutlined','HeartOutlined','HistoryOutlined','HomeOutlined','HourglassOutlined','IdcardOutlined','ImportOutlined','InboxOutlined','InsertRowAboveOutlined','InsertRowBelowOutlined','InsertRowLeftOutlined','InsertRowRightOutlined','InsuranceOutlined','InteractionOutlined','KeyOutlined','LaptopOutlined','LayoutOutlined','LikeOutlined','LineOutlined','LinkOutlined','Loading3QuartersOutlined','LoadingOutlined','LockOutlined','MacCommandOutlined','MailOutlined','ManOutlined','MedicineBoxOutlined','MehOutlined','MenuOutlined','MergeCellsOutlined','MessageOutlined','MobileOutlined','MoneyCollectOutlined','MonitorOutlined','MoreOutlined','NodeCollapseOutlined','NodeExpandOutlined','NodeIndexOutlined','NotificationOutlined','NumberOutlined','OneToOneOutlined','PaperClipOutlined','PartitionOutlined','PayCircleOutlined','PercentageOutlined','PhoneOutlined','PictureOutlined','PlaySquareOutlined','PoundCircleOutlined','PoundOutlined','PoweroffOutlined','PrinterOutlined','ProfileOutlined','ProjectOutlined','PropertySafetyOutlined','PullRequestOutlined','PushpinOutlined','QrcodeOutlined','ReadOutlined','ReconciliationOutlined','RedEnvelopeOutlined','ReloadOutlined','RestOutlined','RobotOutlined','RocketOutlined','RotateLeftOutlined','RotateRightOutlined','SafetyCertificateOutlined','SafetyOutlined','SaveOutlined','ScanOutlined','ScheduleOutlined','SearchOutlined','SecurityScanOutlined','SelectOutlined','SendOutlined','SettingOutlined','ShakeOutlined','ShareAltOutlined','ShopOutlined','ShoppingCartOutlined','ShoppingOutlined','SisternodeOutlined','SkinOutlined','SmileOutlined','SolutionOutlined','SoundOutlined','SplitCellsOutlined','StarOutlined','SubnodeOutlined','SwitcherOutlined','SyncOutlined','TableOutlined','TabletOutlined','TagOutlined','TagsOutlined','TeamOutlined','ThunderboltOutlined','ToTopOutlined','ToolOutlined','TrademarkCircleOutlined','TrademarkOutlined','TransactionOutlined','TranslationOutlined','TrophyOutlined','UngroupOutlined','UnlockOutlined','UploadOutlined','UsbOutlined','UserAddOutlined','UserDeleteOutlined','UserOutlined','UserSwitchOutlined','UsergroupAddOutlined','UsergroupDeleteOutlined','VerifiedOutlined','VideoCameraAddOutlined','VideoCameraOutlined','WalletOutlined','WhatsAppOutlined','WifiOutlined','WomanOutlined'] +const directionIcons = ['StepBackwardOutlined','StepForwardOutlined','FastBackwardOutlined','FastForwardOutlined','ShrinkOutlined','ArrowsAltOutlined','DownOutlined','UpOutlined','LeftOutlined','RightOutlined','CaretUpOutlined','CaretDownOutlined','CaretLeftOutlined','CaretRightOutlined','UpCircleOutlined','DownCircleOutlined','LeftCircleOutlined','RightCircleOutlined','DoubleRightOutlined','DoubleLeftOutlined','VerticalLeftOutlined','VerticalRightOutlined','VerticalAlignTopOutlined','VerticalAlignMiddleOutlined','VerticalAlignBottomOutlined','ForwardOutlined','BackwardOutlined','RollbackOutlined','EnterOutlined','RetweetOutlined','SwapOutlined','SwapLeftOutlined','SwapRightOutlined','ArrowUpOutlined','ArrowDownOutlined','ArrowLeftOutlined','ArrowRightOutlined','PlayCircleOutlined','UpSquareOutlined','DownSquareOutlined','LeftSquareOutlined','RightSquareOutlined','LoginOutlined','LogoutOutlined','MenuFoldOutlined','MenuUnfoldOutlined','BorderBottomOutlined','BorderHorizontalOutlined','BorderInnerOutlined','BorderOuterOutlined','BorderLeftOutlined','BorderRightOutlined','BorderTopOutlined','BorderVerticleOutlined','PicCenterOutlined','PicLeftOutlined','PicRightOutlined','RadiusBottomleftOutlined','RadiusBottomrightOutlined','RadiusUpleftOutlined','RadiusUprightOutlined','FullscreenOutlined','FullscreenExitOutlined'] +const suggestionIcons = ['QuestionOutlined','QuestionCircleOutlined','PlusOutlined','PlusCircleOutlined','PauseOutlined','PauseCircleOutlined','MinusOutlined','MinusCircleOutlined','PlusSquareOutlined','MinusSquareOutlined','InfoOutlined','InfoCircleOutlined','ExclamationOutlined','ExclamationCircleOutlined','CloseOutlined','CloseCircleOutlined','CloseSquareOutlined','CheckOutlined','CheckCircleOutlined','CheckSquareOutlined','ClockCircleOutlined','WarningOutlined','IssuesCloseOutlined','StopOutlined'] +const editIcons = ['EditOutlined','FormOutlined','CopyOutlined','ScissorOutlined','DeleteOutlined','SnippetsOutlined','DiffOutlined','HighlightOutlined','AlignCenterOutlined','AlignLeftOutlined','AlignRightOutlined','BgColorsOutlined','BoldOutlined','ItalicOutlined','UnderlineOutlined','StrikethroughOutlined','RedoOutlined','UndoOutlined','ZoomInOutlined','ZoomOutOutlined','FontColorsOutlined','FontSizeOutlined','LineHeightOutlined','DashOutlined','SmallDashOutlined','SortAscendingOutlined','SortDescendingOutlined','DragOutlined','OrderedListOutlined','UnorderedListOutlined','RadiusSettingOutlined','ColumnWidthOutlined','ColumnHeightOutlined'] +const dataIcons = ['AreaChartOutlined','PieChartOutlined','BarChartOutlined','DotChartOutlined','LineChartOutlined','RadarChartOutlined','HeatMapOutlined','FallOutlined','RiseOutlined','StockOutlined','BoxPlotOutlined','FundOutlined','SlidersOutlined'] +const logoIcons = ['AndroidOutlined','AppleOutlined','WindowsOutlined','IeOutlined','ChromeOutlined','GithubOutlined','AliwangwangOutlined','DingdingOutlined','WeiboSquareOutlined','WeiboCircleOutlined','TaobaoCircleOutlined','Html5Outlined','WeiboOutlined','TwitterOutlined','WechatOutlined','YoutubeOutlined','AlipayCircleOutlined','TaobaoOutlined','SkypeOutlined','QqOutlined','MediumWorkmarkOutlined','GitlabOutlined','MediumOutlined','LinkedinOutlined','GooglePlusOutlined','DropboxOutlined','FacebookOutlined','CodepenOutlined','CodeSandboxOutlined','AmazonOutlined','GoogleOutlined','CodepenCircleOutlined','AlipayOutlined','AntDesignOutlined','AntCloudOutlined','AliyunOutlined','ZhihuOutlined','SlackOutlined','SlackSquareOutlined','BehanceOutlined','BehanceSquareOutlined','DribbbleOutlined','DribbbleSquareOutlined','InstagramOutlined','YuqueOutlined','AlibabaOutlined','YahooOutlined','RedditOutlined','SketchOutlined'] + +class IconModal extends PureComponent { + state = {} + + render() { + const { visible, handleOk, handleCancel, handleSearch, selectIcon, selectedItem, searchItem } = this.props + let webIconsTem + let directionIconsTem + let suggestionIconsTem + let editIconsTem + let dataIconsTem + let logoIconsTem + + if (searchItem) { + webIconsTem = webIcons.filter(item=>item.toLocaleLowerCase().indexOf(searchItem?.toLocaleLowerCase()) > -1) + editIconsTem = editIcons.filter(item=>item.toLocaleLowerCase().indexOf(searchItem?.toLocaleLowerCase()) > -1) + dataIconsTem = dataIcons.filter(item=>item.toLocaleLowerCase().indexOf(searchItem?.toLocaleLowerCase()) > -1) + logoIconsTem = logoIcons.filter(item=>item.toLocaleLowerCase().indexOf(searchItem?.toLocaleLowerCase()) > -1) + } else { + webIconsTem = webIcons + editIconsTem = editIcons + dataIconsTem = dataIcons + logoIconsTem = logoIcons + } + + const tabItems = [ + { + key: '1', + label: '网站通用图标', + children: ( +
    + {webIconsTem.map((item) => { + return + { + createElement( + Icon[item],{ + className: classNames(styles.icon, {[styles.selectedicon]: item?.toLocaleLowerCase() === selectedItem?.toLocaleLowerCase()}), + onClick: () => { selectIcon(item) } + } + ) + } + + })} +
    + ) + }, + { + key: '2', + label: '编辑类图标', + children: ( +
    + {editIconsTem.map((item) => { + return + { + createElement( + Icon[item],{ + className: classNames(styles.icon, {[styles.selectedicon]: item?.toLocaleLowerCase() === selectedItem?.toLocaleLowerCase()}), + onClick: () => { selectIcon(item) } + } + ) + } + + })} +
    + ) + }, + { + key: '3', + label: '数据类图标', + children: ( +
    + {dataIconsTem.map((item) => { + return + { + createElement( + Icon[item],{ + className: classNames(styles.icon, {[styles.selectedicon]: item?.toLocaleLowerCase() === selectedItem?.toLocaleLowerCase()}), + onClick: () => { selectIcon(item) } + } + ) + } + + })} +
    + ) + }, + { + key: '4', + label: '品牌和标识', + children: ( +
    + {logoIconsTem.map((item) => { + return + { + createElement( + Icon[item],{ + className: classNames(styles.icon, {[styles.selectedicon]: item?.toLocaleLowerCase() === selectedItem?.toLocaleLowerCase()}), + onClick: () => { selectIcon(item) } + } + ) + } + + })} +
    + ) + } + ] + + return ( + + + + + + ) + } +} + +export default IconModal diff --git a/src/components/IconModal/index.less b/src/components/IconModal/index.less new file mode 100644 index 0000000..6d4df4a --- /dev/null +++ b/src/components/IconModal/index.less @@ -0,0 +1,21 @@ +.icon { + width: 40px; + height: 40px; + font-size: 20px; + line-height: 40px; + text-align: center; + justify-content: center; +} + +.icon:hover { + cursor: pointer; + background: #515151; + color: honeydew; + border-radius: 5px; +} + +.selectedicon { + background: #515151; + color: honeydew; + border-radius: 5px; +} diff --git a/src/components/LineWrap/index.js b/src/components/LineWrap/index.js new file mode 100644 index 0000000..a04bd5a --- /dev/null +++ b/src/components/LineWrap/index.js @@ -0,0 +1,25 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import { Tooltip } from 'antd' +import styles from './index.less' + +export default class LineWrap extends PureComponent { + static propTypes = { + title: PropTypes.string, + lineClampNum: PropTypes.number, + // colWidth: PropTypes.string + } + + render() { + const { title, lineClampNum, colWidth } = this.props + const lineWidth = colWidth !== '' && colWidth !== undefined ? colWidth : '80px' + + return ( + + + {title} + + + ) + } +} diff --git a/src/components/LineWrap/index.less b/src/components/LineWrap/index.less new file mode 100644 index 0000000..d51f70d --- /dev/null +++ b/src/components/LineWrap/index.less @@ -0,0 +1,10 @@ +.col { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + display: inline-block; +} + +.content > span { + display: block; +} diff --git a/src/components/Loading/index.js b/src/components/Loading/index.js new file mode 100644 index 0000000..93c03c6 --- /dev/null +++ b/src/components/Loading/index.js @@ -0,0 +1,26 @@ +import './index.less' + +const Loading = () => { + return ( +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + 页面初始化中...... +
    +
    +
    + ) +} + +export default Loading diff --git a/src/components/Loading/index.less b/src/components/Loading/index.less new file mode 100644 index 0000000..0398698 --- /dev/null +++ b/src/components/Loading/index.less @@ -0,0 +1,126 @@ +.um_content { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(255,255,255); + z-index: 1200; + overflow: auto; + margin: 0 auto; + width: 100%; + height: 100vh; + + .spinner { + position: absolute; + top: 50%; + left: 50%; + margin-right: -50%; + transform: translate(-50%, -50%); + width: 120px; + height: 80px; + + #loading { + position: relative; + width: 50px; + height: 50px; + margin: 0 auto; + + .spinDot { + width: 0.3em; + height: 0.3em; + border-radius: 2px; + background: #68b2ce; + position: absolute; + animation: spinDot linear 0.8s infinite; + -webkit-animation: spinDot linear 0.8s infinite; + + &:nth-child(1) { + left: 24px; + top: 2px; + animation-delay: 0s; + } + + &:nth-child(2) { + left: 40px; + top: 8px; + animation-delay: 0.1s; + } + + &:nth-child(3) { + left: 47px; + top: 24px; + animation-delay: 0.1s; + } + + &:nth-child(4) { + left: 40px; + top: 40px; + animation-delay: 0.2s; + } + + &:nth-child(5) { + left: 24px; + top: 47px; + animation-delay: 0.4s; + } + + &:nth-child(6) { + left: 8px; + top: 40px; + animation-delay: 0.5s; + } + + &:nth-child(7) { + left: 2px; + top: 24px; + animation-delay: 0.6s; + } + + &:nth-child(8) { + left: 8px; + top: 8px; + animation-delay: 0.7s; + } + } + + @keyframes spinDot { + 0%, 40%, 100% { + transform: scale(1); + } + + 20% { + transform: scale(3); + } + } + + @-webkit-keyframes spinDot { + 0%, 40%, 100% { + transform: scale(1); + } + + 20% { + transform: scale(3); + } + } + } + } + + .loadingInfo { + width: 140px; + height: 20px; + position: absolute; + top: 70%; + text-align: center; + text-indent: 4px; + + > span { + font-family: "Microsoft YaHei", Helvetica, Arial, Lucida Grande, Tahoma, sans-serif, Raleway, sans-serif; + color: black; + display: block; + font-size: 16px; + text-align: center; + width: 100%; + } + } +} diff --git a/src/components/MyIcon/index.js b/src/components/MyIcon/index.js new file mode 100644 index 0000000..3cadc1d --- /dev/null +++ b/src/components/MyIcon/index.js @@ -0,0 +1,7 @@ +import { createFromIconfontCN } from '@ant-design/icons' +import defaultSettings from '../../../config/defaultSettings' + +export default createFromIconfontCN({ + // 该地址为iconfont中的项目地址,根据实际进行修改 + scriptUrl: defaultSettings.iconfontUrl +}) diff --git a/src/components/NotificationTemplate/index.js b/src/components/NotificationTemplate/index.js new file mode 100644 index 0000000..bbaa78d --- /dev/null +++ b/src/components/NotificationTemplate/index.js @@ -0,0 +1,53 @@ +import { PureComponent } from 'react' +import './index.less' + +class NotificationTemplate extends PureComponent { + state = { + folding: false, + showFoldingBtn: false, + cloak: true + } + + /** + * 判断正文高度以显示全文/收起按钮 + */ + componentWillMount() { + /*const height = document.querySelector('.deptContentHidden')?.offsetHeight + this.setState({ + showFoldingBtn: height > 1.5 * 16 * 1, // 内容超过1行 + cloak: false + })*/ + } + + /** + * 全文/收起 + */ + onFoldBtnClick = () => { + this.setState((prevState) => ({ + folding: !prevState.folding + })) + } + + render() { + const { message } = this.props + const { folding, showFoldingBtn } = this.state + + return ( +
    +
    + {message} +
    + + {!folding && } + + {folding &&
    + 收起 +
    } +
    + ) + } +} + +export default NotificationTemplate diff --git a/src/components/NotificationTemplate/index.less b/src/components/NotificationTemplate/index.less new file mode 100644 index 0000000..0c0b267 --- /dev/null +++ b/src/components/NotificationTemplate/index.less @@ -0,0 +1,23 @@ +.deptContentHidden { + font-family: PingFangSC-Regular, serif; + font-size: 14px; + color: #666666; + letter-spacing: 0.2px; + text-align: justify; + line-height: 22px; + height: 45px; + overflow: hidden; +} + +.deptContent { + font-family: PingFangSC-Regular, serif; + font-size: 14px; + color: #666666; + letter-spacing: 0.2px; + text-align: justify; + line-height: 22px; +} + +.more-btn { + float: right; +} diff --git a/src/components/NumberInput/NumberInput/index.js b/src/components/NumberInput/NumberInput/index.js new file mode 100644 index 0000000..dba11b8 --- /dev/null +++ b/src/components/NumberInput/NumberInput/index.js @@ -0,0 +1,59 @@ +import React, {PureComponent} from 'react'; +import { Input } from 'antd'; +import styles from '../index.less' + +const formatNumber = (value) => { + value += ''; + const list = value.split('.'); + const prefix = list[0].charAt(0) === '-' ? '-' : ''; + let num = prefix ? list[0].slice(1) : list[0]; + let result = ''; + while (num.length > 3) { + result = `,${num.slice(-3)}${result}`; + num = num.slice(0, num.length - 3); + } + if (num) { + result = num + result; + } + return `${prefix}${result}${list[1] ? `.${list[1]}` : ''}`; +} + +class NumberInput extends PureComponent { + onChange = e => { + const { value } = e.target; + const reg = /^\d*?$/; + if ((!isNaN(value) && reg.test(value)) || value === '') { + this.props.onChange(value); + } + }; + + // '.' at the end or only '-' in the input box. + onBlur = () => { + const { value, onBlur, onChange } = this.props; + let valueTemp = value; + if (value.charAt(value.length - 1) === '.') { + valueTemp = value.slice(0, -1); + } + onChange(valueTemp.replace(/0*(\d+)/, '$1')); + if (onBlur) { + onBlur(); + } + }; + + render() { + const { placeholder, min } = this.props; + + return ( + + ); + } +} + +export default NumberInput; diff --git a/src/components/NumberInput/NumericInput/index.js b/src/components/NumberInput/NumericInput/index.js new file mode 100644 index 0000000..17ee4fa --- /dev/null +++ b/src/components/NumberInput/NumericInput/index.js @@ -0,0 +1,102 @@ +import React, {PureComponent} from 'react'; +import { Tooltip, InputNumber } from 'antd'; +import styles from '../index.less' +import {NumberInput} from "@/components/NumberInput"; + + +const formatNumber = (value) => { + value += ''; + const list = value.split('.'); + const prefix = list[0].charAt(0) === '-' ? '-' : ''; + let num = prefix ? list[0].slice(1) : list[0]; + let result = ''; + while (num.length > 3) { + result = `,${num.slice(-3)}${result}`; + num = num.slice(0, num.length - 3); + } + if (num) { + result = num + result; + } + return `${prefix}${result}${list[1] ? `.${list[1]}` : ''}`; +} + +/** + * 金额处理 + */ +class NumericInput extends PureComponent { + + state = { + disabled: false, + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if(undefined != nextProps.disabled && nextProps.disabled !== this.props.disabled) { + this.setState({disabled: nextProps.disabled}); + } + } + + onChange = e => { + // const { value } = e.target; // input 取值方式 + const value = e + ''; + const reg = /^\d*(\.\d*)?$/; + if ((!isNaN(value) && reg.test(value)) || value === '') { + this.props.onChange(value); + } + }; + + // '.' at the end or only '-' in the input box. + onBlur = () => { + const { value, onBlur, onChange } = this.props; + + if(value) { + + let valueTemp = value + ''; + if (typeof(valueTemp) == 'string' && valueTemp?.charAt(valueTemp.length - 1) === '.') { + valueTemp = valueTemp.slice(0, -1); + } + onChange(valueTemp?.replace(/0*(\d+)/, '$1')); + if (onBlur) { + onBlur(); + } + } + + + }; + + render() { + const { value, placeholder, min,precision } = this.props; + const valueTem = value || value != undefined ? value + '' : ''; + const {disabled} = this.state; + const title = valueTem ? ( + {valueTem !== '-' ? '¥' + formatNumber(valueTem) : '-'} + ) : ( + '请输入' + ); + return ( + + `¥${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',').replace(/¥+/g, "¥")} + parser={value => `${value}`.replace(/\¥|\$\sR?|(,*)/g, '').replace(/¥+/g, "")} + precision={precision? precision : 2} + maxLength={14} + disabled={disabled} + // addonAfter={('元')} + /> + + ); + } +} + +export default NumericInput; diff --git a/src/components/NumberInput/index.js b/src/components/NumberInput/index.js new file mode 100644 index 0000000..dee4dfd --- /dev/null +++ b/src/components/NumberInput/index.js @@ -0,0 +1,12 @@ +import NumberInput from './NumberInput'; +import NumericInput from './NumericInput'; + +const input = { + NumberInput, + NumericInput, +}; +export { + input as default, + NumberInput, + NumericInput, +}; diff --git a/src/components/NumberInput/index.less b/src/components/NumberInput/index.less new file mode 100644 index 0000000..d722f82 --- /dev/null +++ b/src/components/NumberInput/index.less @@ -0,0 +1,10 @@ + + +.numeric-input .ant-tooltip-inner { + min-width: 32px; + min-height: 37px; +} + +.numeric-input .numeric-input-title { + font-size: 14px; +} diff --git a/src/components/SelectDeptTree/index.js b/src/components/SelectDeptTree/index.js new file mode 100644 index 0000000..84f9521 --- /dev/null +++ b/src/components/SelectDeptTree/index.js @@ -0,0 +1,125 @@ +import {PureComponent} from 'react'; +import {TreeSelect} from 'antd'; +import { connect, history } from '@umijs/max'; + +const {TreeNode} = TreeSelect; + +@connect(({globaldata, loading}) => ({ + globaldata, + loading: loading.models.globaldata +})) + +class SelectDeptTree extends PureComponent { + state = { + value: this.props.value, + treeData: [ + {title: '部门', id: '000000000000', key: '000000000000', value: '000000000000', dept_id: '000000000000', dept_code: "000"}, + ], + }; + + UNSAFE_componentWillReceiveProps(nextProps) { + if(undefined != nextProps.value && nextProps.value !== this.props.value) { + this.setState({value: nextProps.value}); + } + } + + constructor(props) { + super(props); + } + + onChange = (value, label, extra) => { + + if (value != '000000000000') { + + const {selectedDeptTreeValue} = this.props; + if(selectedDeptTreeValue){ + const selectDept = { + dept_code: value, + title: label[0], + } + selectedDeptTreeValue(selectDept, label, extra); + } + this.setState({value}); + } + + }; + + onSelect = value => { + const {onSelect} = this.props; + if(onSelect){ + onSelect(value) + } + } + + onTreeExpand = expandedKeys => { + const {onTreeExpand} = this.props; + if(onTreeExpand){ + onTreeExpand(expandedKeys) + } + } + + updateTreeData(list, key, children) { + return list.map(node => { + if (node.key === key) { + return { + ...node, + children, + }; + } else if (node.children) { + return { + ...node, + children: this.updateTreeData(node.children, key, children), + }; + } + return node; + }); + } + + onLoadData = ({id, key, children}) => { + const {dispatch} = this.props; + const params = { + parentid: id, + }; + return new Promise((resolve) => { + if (children) { + resolve(); + return; + } + dispatch({ + type: 'globaldata/get_prodepttree_by_parentid_for_prodept', + payload: params, + callback: (res) => { + if(res.success == true) { + // treeNode.props.dataRef.children = res.data; + this.setState({ + treeData: this.updateTreeData(this.state.treeData, key, res.data) + }); + resolve(); + } + + } + }); + }); + }; + + render() { + const { treeData, value } = this.state; + const { placeholder } = this.props; + return ( + + ); + } +} + +export default SelectDeptTree; diff --git a/src/components/SelectEbookArticleTypeTree/index.js b/src/components/SelectEbookArticleTypeTree/index.js new file mode 100644 index 0000000..bbb5371 --- /dev/null +++ b/src/components/SelectEbookArticleTypeTree/index.js @@ -0,0 +1,119 @@ +import { PureComponent } from 'react' +import { TreeSelect } from 'antd' +const { TreeNode } = TreeSelect + +class SelectEbookArticleTypeTree extends PureComponent { + state = { + value: undefined, + treeData: [ + { title: '分类', key: '000000000000', id: '000000000000', levelcode: '000000000000', value: '000000000000' } + ] + } + + constructor(props) { + super(props) + this.state.value = props?.value + } + + onChange = (value, label, extra) => { + if (extra.key !== '000000000000') { + const { selectedEbookArticleTypeTreeValue } = this.props + selectedEbookArticleTypeTreeValue({key: value, label: label[0]}) + this.setState({ value: {label: label[0]} }) + } + } + + onSelect = value => { + + } + + onTreeExpand = expandedKeys => { + + } + + updateTreeData(list, key, children) { + return list.map(node => { + if (node.key === key) { + return { + ...node, + key: node.id, + value: node.value, + children + } + } else if (node.children) { + return { + ...node, + key: node.id, + value: node.value, + children: this.updateTreeData(node.children, key, children) + } + } + + return node + }) + } + + onLoadData = ({id, key, children}) => { + const { dispatch } = this.props + const params = { + parentid: id + } + + return new Promise((resolve) => { + if (children) { + resolve() + return + } + + dispatch({ + type: 'globaldata/get_proebookarticletypetree_by_parentid', + payload: params, + callback: (res) => { + if(res.success === true) { + // treeNode.props.dataRef.children = res.list + this.setState({ + treeData: this.updateTreeData(this.state.treeData, key, res.list) + }) + + resolve() + } + } + }) + }) + } + + renderTreeNodes = data => + data.map(item => { + if (item.children) { + return ( + + {this.renderTreeNodes(item.children)} + + ) + } + + return + }) + + render() { + const { value } = this.props + const { treeData } = this.state + + return ( + + ) + } +} + +export default SelectEbookArticleTypeTree diff --git a/src/components/SelectMenuTree/index.js b/src/components/SelectMenuTree/index.js new file mode 100644 index 0000000..750cd56 --- /dev/null +++ b/src/components/SelectMenuTree/index.js @@ -0,0 +1,122 @@ +import { PureComponent } from 'react' +import { TreeSelect } from 'antd' +import { connect } from '@umijs/max' + +const emptyValue = { label: null, value: null } + +@connect(({ globaldata, loading }) => ({ + globaldata, + loading: loading.models.globaldata +})) + +class SelectMenuTree extends PureComponent { + state = { + treeData: [], + cacheTreeData: [], + value: emptyValue, + cacheValue: emptyValue + } + + static getDerivedStateFromProps (nextProps, state) { + let newState = {} + + if (!!nextProps.treeData && nextProps.treeData !== state.cacheTreeData) { + newState = { + ...newState, + treeData: nextProps?.treeData ?? [], + cacheTreeData: nextProps?.treeData ?? [] + } + } + + if (!!nextProps.value && nextProps.value !== state.cacheValue) { + newState = { + ...newState, + value: nextProps?.value ?? emptyValue, + cacheValue: nextProps?.cacheValue ?? emptyValue + } + } + + return Object.keys(newState)?.length > 0 ? newState : null + } + + updateTreeData(list, key, children) { + return list.map(node => { + if (node.id === key) { + return { + ...node, + children + } + } else if (node.children) { + return { + ...node, + children: this.updateTreeData(node.children, key, children) + } + } + + return node + }) + } + + loadData = ({ id, key, children }) => { + const { dispatch, params: loadParams } = this.props + const params = { + parentid: id, + ...loadParams + } + + return new Promise(resolve => { + if (children) { + resolve() + return + } + + dispatch({ + type: 'globaldata/get_promenutree_by_parentid_for_promenu', + payload: params, + callback: res => { + if(res.success) { + this.setState({ + treeData: this.updateTreeData(this.state.treeData, key, res.list) + }) + + resolve() + } + } + }) + }) + } + + onChange = (data, _, extra) => { + const { selectMenuTree, onChange } = this.props + const { label, value } = data ?? {} + + if (value === '000000000000') return + const params = { label: label ?? null, value: value ?? null } + + selectMenuTree && selectMenuTree(params, label, extra) + !selectMenuTree && onChange && onChange(params) + this.setState({ value: params }) + } + + render () { + const { treeData, value } = this.state + + return ( + + ) + } +} + +export default SelectMenuTree diff --git a/src/components/SelectOptionTree/index.js b/src/components/SelectOptionTree/index.js new file mode 100644 index 0000000..77dc43f --- /dev/null +++ b/src/components/SelectOptionTree/index.js @@ -0,0 +1,91 @@ +import {PureComponent} from 'react'; +import {TreeSelect} from 'antd'; + +const {TreeNode} = TreeSelect; + +class SelectOptionTree extends PureComponent { + state = { + value: undefined, + treeData: this.props.treeData || [] + } + + constructor(props) { + super(props); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if(undefined !== nextProps.value && nextProps.value !== this.props.value) { + this.setState({value: nextProps.value}); + } + if(undefined !== nextProps.treeData) { + this.setState({treeData: nextProps.treeData}); + } + } + + onSelect = (value) => { + const {onChange} = this.props; + + if(onChange){ + onChange(value); + } + } + + onChangeValue = (value) => { + const {onChange} = this.props; + let values = ''; + + if (undefined !== value) { + values = value; + } + if(onChange){ + onChange(values); + } + this.setState({ + value: values + }) + } + + renderTreeNodes = data => + Array.from(data).map(item => { + const {type} = this.props; + let val = item.value ? item.value : null; + let title = `${item.title ? item.title : ''}[${val}]`; + + if (item.children?.length > 0) { + item.disabled = type !== 'area'; + + return ( + + {this.renderTreeNodes(item.children)} + + ); + } + return ; + }); + + render() { + const { placeholder, disabled, selectWidth } = this.props; + const { treeData, value } = this.state; + + return ( + + {this.renderTreeNodes(treeData)} + + ); + } +} + +export default SelectOptionTree; diff --git a/src/components/SelectOrganTree/index.js b/src/components/SelectOrganTree/index.js new file mode 100644 index 0000000..6e32743 --- /dev/null +++ b/src/components/SelectOrganTree/index.js @@ -0,0 +1,133 @@ +import {PureComponent} from 'react' +import {TreeSelect} from 'antd' +import {connect} from '@umijs/max' + +const {TreeNode} = TreeSelect + +@connect(({globaldata, loading}) => ({ + globaldata, + loading: loading.models.globaldata, +})) +class SelectOrganTree extends PureComponent { + state = { + value: undefined, + treeData: [ + { title: '机构', key: '000000000000', value: '000000000000', org_id: '000000000000', org_code: '0000', id: '000000000000' } + ], + } + + constructor(props) { + super(props) + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if(undefined !== nextProps.value && nextProps.value !== this.props.value) { + this.setState({value: nextProps.value}) + } + } + + onChange = (value, label, extra) => { + if (value !== '000000000000') { + const {selectedOrganTreeValue} = this.props + if(selectedOrganTreeValue){ + const selectDept = { + org_code: value, + title: label[0] + } + selectedOrganTreeValue(selectDept, label, extra) + } + this.setState({value}) + } + } + + onSelect = value => { + const {onSelect} = this.props + if(onSelect){ + onSelect(value) + } + } + + onTreeExpand = expandedKeys => { + const {onTreeExpand} = this.props + if(onTreeExpand){ + onTreeExpand(expandedKeys) + } + } + + updateTreeData(list, key, children) { + return list.map(node => { + if (node.value === key) { + return { + ...node, + children + } + } else if (node.children) { + return { + ...node, + children: this.updateTreeData(node.children, key, children) + } + } + + return node + }) + } + + onLoadData = ({ key, children, id }) => { + const { dispatch } = this.props + const params = { parentid: id } + + return new Promise((resolve) => { + if (children) { + resolve() + return + } + + dispatch({ + type: 'globaldata/get_proorgtree_by_parentid_for_proorg', + payload: params, + callback: (res) => { + if(res.success === true) { + this.setState({ + treeData: this.updateTreeData(this.state.treeData, key, res.list) + }) + + resolve() + } + } + }) + }) + } + + renderTreeNodes = data => + data.map(item => { + if (item.children) { + return ( + + {this.renderTreeNodes(item.children)} + + ) + } + return + }) + + render() { + const { treeData, value } = this.state + + return ( + + ) + } +} + +export default SelectOrganTree diff --git a/src/components/StandardList/index.js b/src/components/StandardList/index.js new file mode 100644 index 0000000..23ddda5 --- /dev/null +++ b/src/components/StandardList/index.js @@ -0,0 +1,95 @@ +import { PureComponent } from 'react' +import { List } from 'antd' +import styles from './index.less' + +const { Item: ListItem } = List + +class StandardList extends PureComponent { + state = { + } + + constructor(props) { + super(props) + const { selectedRows, data = {} } = props + } + + /*static getDerivedStateFromProps(nextProps) { + // clean state + if (nextProps.selectedRows.length === 0) { + return { + selectedRowKeys: [], + } + } + return null + }*/ + + handleListChange = (pagination) => { + const { onChange } = this.props + /*this.setState({ + indeterminate: false, + checkAll: false, + })*/ + + onChange && onChange(pagination) + } + + onCheckAllChange = e => { + const { data = {}, onSelectRow } = this.props + + this.setState({ + selectedRows: e.target.checked ? data.list : [], + indeterminate: false, + checkAll: e.target.checked + }) + + onSelectRow && onSelectRow(e.target.checked ? data.list : []) + } + + onChange = checkedValues => { + const { data = {}, onSelectRow } = this.props + + this.setState({ + selectedRows: checkedValues, + indeterminate: !!checkedValues.length && checkedValues.length < data.list.length, + checkAll: checkedValues.length === data.list.length + }) + + onSelectRow && onSelectRow(checkedValues) + } + + render() { + const { data = {}, rowKey, loading, actions, details, className } = this.props + const { list = [], pagination } = data + const paginationProps = { + showSizeChanger: true, + showQuickJumper: true, + ...pagination, + pageSizeOptions: [8, 16, 40, 80], + onChange: page => { + pagination.current = page + this.handleListChange(pagination) + } + } + + return ( +
    + {list && + ( + + {details(item)} + + )} + /> + } +
    + ) + } +} + +export default StandardList diff --git a/src/components/StandardList/index.less b/src/components/StandardList/index.less new file mode 100644 index 0000000..6cb759a --- /dev/null +++ b/src/components/StandardList/index.less @@ -0,0 +1,53 @@ +@heading-color: fade(#000, 85%); +@primary-color: #1890ff; +@text-color-secondary: fade(#000, 45%); + +.card { + :global { + .ant-card-meta-title { + margin-bottom: 4px; + + & > a { + display: inline-block; + max-width: 100%; + color: @heading-color; + } + } + + .ant-card-meta-description { + height: 44px; + overflow: hidden; + line-height: 22px; + } + } + + &:hover { + :global { + .ant-card-meta-title > a { + color: @primary-color; + } + } + } +} + +.cardItemContent { + display: flex; + height: 20px; + margin-top: 16px; + margin-bottom: -4px; + line-height: 20px; + + & > span { + flex: 1; + color: @text-color-secondary; + font-size: 12px; + } + + .avatarList { + flex: 0 1 auto; + } +} + +.cardList { + margin-top: 24px; +} diff --git a/src/components/StandardTable/index.d.ts b/src/components/StandardTable/index.d.ts new file mode 100644 index 0000000..d9addef --- /dev/null +++ b/src/components/StandardTable/index.d.ts @@ -0,0 +1,19 @@ +import React from 'react'; +import { PaginationConfig, SorterResult, TableCurrentDataSource } from 'antd/lib/table'; + +export interface StandardTableProps { + columns: any; + onSelectRow: (row: any) => void; + data: any; + rowKey?: string; + selectedRows: any[]; + onChange?: ( + pagination: PaginationConfig, + filters: Record, + sorter: SorterResult, + extra?: TableCurrentDataSource + ) => void; + loading?: boolean; +} + +export default class StandardTable extends React.Component {} diff --git a/src/components/StandardTable/index.js b/src/components/StandardTable/index.js new file mode 100644 index 0000000..94a3dc6 --- /dev/null +++ b/src/components/StandardTable/index.js @@ -0,0 +1,195 @@ +import React, {Fragment, PureComponent} from 'react'; +import {Alert, Table} from 'antd'; +import styles from './index.less'; + +function initTotalList(columns) { + const totalList = []; + columns.forEach(column => { + if (column.needTotal) { + totalList.push({...column, total: 0}); + } + }); + return totalList; +} + +class StandardTable extends PureComponent { + constructor(props) { + super(props); + const {columns} = props; + const needTotalList = initTotalList(columns); + + this.state = { + selectedRowKeys: [], + selectedRows: [], + needTotalList, + }; + } + + static getDerivedStateFromProps(nextProps) { + // clean state + if (nextProps.selectedRows && nextProps.selectedRows.length === 0) { + const needTotalList = initTotalList(nextProps.columns); + return { + selectedRowKeys: [], + selectedRows: [], + needTotalList, + }; + } + return null; + } + + handleRowSelectChange = (selectedRowKeys, selectedRows) => { + let {needTotalList} = this.state; + needTotalList = needTotalList.map(item => ({ + ...item, + total: selectedRows.reduce((sum, val) => sum + parseFloat(val[item.dataIndex], 10), 0), + })); + const {onSelectRow} = this.props; + if (onSelectRow) { + onSelectRow(selectedRows); + } + this.setState({selectedRowKeys, selectedRows, needTotalList}); + }; + + handleTableChange = (pagination, filters, sorter) => { + const {onChange} = this.props; + if (onChange) { + onChange(pagination, filters, sorter); + } + }; + + cleanSelectedKeys = () => { + this.handleRowSelectChange([], []); + }; + + // 单击行事件 + rowClick = (record) => { + const {selectionType, rowKey} = this.props; + const {selectedRowKeys, selectedRows} = this.state; + const key = record[rowKey] || record.key; + + let newSelectedRowKeys = []; + let newSelectedRows = []; + + + if (selectedRowKeys.includes(key)) { + newSelectedRowKeys = selectedRowKeys.filter(item => item !== key) + newSelectedRows = selectedRows.filter(item => item.id !== key) + } else { + if("radio" != selectionType) { + newSelectedRowKeys = [...selectedRowKeys, key]; + newSelectedRows = [...selectedRows, record]; + }else { + newSelectedRowKeys = [key]; + newSelectedRows = [record]; + } + + } + + this.handleRowSelectChange(newSelectedRowKeys, newSelectedRows); + } + + // 行操作事件 + rowCallback = (record) => { + const {rowClick, rowDoubleClick, rowContextMenu, rowMouseEnter, rowMouseLeave} = this.props; + return { + onClick: event => { + const node = event.target; + const toolNode = event.currentTarget.lastChild; + const hasNode = toolNode.contains(node); + if (!hasNode) { + this.rowClick(record) + } + if(rowClick) { + rowClick(event, record); + } + }, // 点击行 + onDoubleClick: event => { + if(rowDoubleClick) { + rowDoubleClick(event, record); + } + }, + onContextMenu: event => { + if(rowContextMenu) { + rowContextMenu(event, record); + } + }, + onMouseEnter: event => { + if(rowMouseEnter) { + rowMouseEnter(event, record); + } + }, // 鼠标移入行 + onMouseLeave: event => { + if(rowMouseLeave) { + rowMouseLeave(event, record); + } + }, + }; + + }; + + render() { + const {selectedRowKeys, needTotalList} = this.state; + + const {data = {}, selectionType, rowKey, size, bodyStyle, isshowAlert,...rest} = this.props; + const {list = [], pagination} = data; + const toggleAlert = this.props.showAlert !== '' && this.props.showAlert !== undefined ? this.props.showAlert : true; + const paginationProps = { + showSizeChanger: true, + showQuickJumper: true, + current: pagination?.currentPage == 0 ? 1 : pagination?.currentPage, + ...pagination, + }; + + const rowSelection = { + type: selectionType || "checkbox", + selectedRowKeys, + onChange: this.handleRowSelectChange, + getCheckboxProps: record => ({ + disabled: record.disabled, + }), + }; + + + return ( +
    + { isshowAlert&&(toggleAlert && toggleAlert !== undefined) ? ( +
    + + 已选择 {selectedRowKeys.length} 项   + {needTotalList.map(item => ( + + {item.title} 总计  + + {item.render ? item.render(item.total) : item.total} + + + ))} + + 清空 + + + } + type="info" + showIcon + /> +
    + ) : null} + + + ); + } +} + +export default StandardTable; diff --git a/src/components/StandardTable/index.less b/src/components/StandardTable/index.less new file mode 100644 index 0000000..be2e879 --- /dev/null +++ b/src/components/StandardTable/index.less @@ -0,0 +1,13 @@ + + +.standardTable { + :global { + .ant-table-pagination { + margin-top: 24px; + } + } + + .tableAlert { + margin-bottom: 16px; + } +} diff --git a/src/components/_utils/algorithmTools.js b/src/components/_utils/algorithmTools.js new file mode 100644 index 0000000..a2a25ee --- /dev/null +++ b/src/components/_utils/algorithmTools.js @@ -0,0 +1,11 @@ +export function getNodeByKeyAndCallbackProcess (data, targetkey, callback) { + data.forEach((item, index, arr) => { + if (item.key === targetkey) { + return callback(item, index, arr) + } + + if (item.children) { + return getNodeByKeyAndCallbackProcess(item.children, targetkey, callback) + } + }) +} diff --git a/src/components/_utils/pathTools.js b/src/components/_utils/pathTools.js new file mode 100644 index 0000000..bfb94e7 --- /dev/null +++ b/src/components/_utils/pathTools.js @@ -0,0 +1,6 @@ +// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] +// eslint-disable-next-line import/prefer-default-export +export function urlToList(url) { + const urllist = url.split('/').filter(i => i); + return urllist.map((urlItem, index) => `/${urllist.slice(0, index + 1).join('/')}`); +} diff --git a/src/components/_utils/pathTools.test.js b/src/components/_utils/pathTools.test.js new file mode 100644 index 0000000..a9b9315 --- /dev/null +++ b/src/components/_utils/pathTools.test.js @@ -0,0 +1,17 @@ +import { urlToList } from './pathTools'; + +describe('test urlToList', () => { + it('A path', () => { + expect(urlToList('/userinfo')).toEqual(['/userinfo']); + }); + it('Secondary path', () => { + expect(urlToList('/userinfo/2144')).toEqual(['/userinfo', '/userinfo/2144']); + }); + it('Three paths', () => { + expect(urlToList('/userinfo/2144/addr')).toEqual([ + '/userinfo', + '/userinfo/2144', + '/userinfo/2144/addr', + ]); + }); +}); diff --git a/src/components/borderBox1/index.js b/src/components/borderBox1/index.js new file mode 100644 index 0000000..a016ef3 --- /dev/null +++ b/src/components/borderBox1/index.js @@ -0,0 +1,98 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + + +import { deepMerge,deepClone } from '../../datav/usefull/index' + +import useAutoResize from '../../datav/use/autoResize' + +import './style.less' + +const border = ['left-top', 'right-top', 'left-bottom', 'right-bottom'] +const defaultColor = ['#4fd2dd', '#235fa7'] + +const BorderBox1 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-1', className), [ + className + ]) + + return ( +
    + + + + + {border.map(borderName => ( + + + + + + + + + + + + ))} + +
    {children}
    +
    + ) +}) + +BorderBox1.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox1 diff --git a/src/components/borderBox1/style.less b/src/components/borderBox1/style.less new file mode 100644 index 0000000..96c6b0a --- /dev/null +++ b/src/components/borderBox1/style.less @@ -0,0 +1,32 @@ +.dv-border-box-1 { + position: relative; + width: 100%; + height: 100%; + + .border { + position: absolute; + display: block; + } + + .right-top { + right: 0px; + transform: rotateY(180deg); + } + + .left-bottom { + bottom: 0px; + transform: rotateX(180deg); + } + + .right-bottom { + right: 0px; + bottom: 0px; + transform: rotateX(180deg) rotateY(180deg); + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox10/index.js b/src/components/borderBox10/index.js new file mode 100644 index 0000000..2e358d5 --- /dev/null +++ b/src/components/borderBox10/index.js @@ -0,0 +1,64 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + +import './style.less' + +const border = ['left-top', 'right-top', 'left-bottom', 'right-bottom'] +const defaultColor = ['#1d48c4', '#d3e1f8'] + +const BorderBox10 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-10', className), [ + className + ]) + + const styles = useMemo(() => ({ + boxShadow: `inset 0 0 25px 3px ${mergedColor[0]}`, + ...style + }), [style, mergedColor]) + + return ( +
    + + + + + {border.map(borderName => ( + + + + ))} +
    {children}
    +
    + ) +}) + +BorderBox10.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox10 diff --git a/src/components/borderBox10/style.less b/src/components/borderBox10/style.less new file mode 100644 index 0000000..401b32e --- /dev/null +++ b/src/components/borderBox10/style.less @@ -0,0 +1,33 @@ +.dv-border-box-10 { + position: relative; + width: 100%; + height: 100%; + border-radius: 6px; + + .dv-border-svg-container { + position: absolute; + display: block; + } + + .right-top { + right: 0px; + transform: rotateY(180deg); + } + + .left-bottom { + bottom: 0px; + transform: rotateX(180deg); + } + + .right-bottom { + right: 0px; + bottom: 0px; + transform: rotateX(180deg) rotateY(180deg); + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox11/index.js b/src/components/borderBox11/index.js new file mode 100644 index 0000000..8bc8449 --- /dev/null +++ b/src/components/borderBox11/index.js @@ -0,0 +1,253 @@ +import React, { useMemo, useRef, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { fade, deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['#8aaafb', '#1f33a2'] + +const BorderBox11 = forwardRef(({ children, className, style, color = [], titleWidth = 250, title = '', backgroundColor = 'transparent' }, ref) => { + const filterId = useRef(`border-box-11-filterId-${uuid()}`).current + + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-11', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {title} + + + + + + + +
    + {children} +
    +
    + ) +}) + +BorderBox11.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + titleWidth: PropTypes.number, + title: PropTypes.string, + backgroundColor: PropTypes.string +} + +export default BorderBox11 diff --git a/src/components/borderBox11/style.less b/src/components/borderBox11/style.less new file mode 100644 index 0000000..dd3ff4f --- /dev/null +++ b/src/components/borderBox11/style.less @@ -0,0 +1,24 @@ +.dv-border-box-11 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + + & > polyline { + fill: none; + stroke-width: 1; + } + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox12/index.js b/src/components/borderBox12/index.js new file mode 100644 index 0000000..8ded4b5 --- /dev/null +++ b/src/components/borderBox12/index.js @@ -0,0 +1,128 @@ +import React, { useMemo, useRef, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { fade, deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['#2e6099', '#7ce7fd'] + +const BorderBox12 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const filterId = useRef(`border-box-12-filterId-${uuid()}`).current + + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-12', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + + { + width && height && + + } + + + + + + + + + +
    + {children} +
    +
    + ) +}) + +BorderBox12.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox12 diff --git a/src/components/borderBox12/style.less b/src/components/borderBox12/style.less new file mode 100644 index 0000000..72ee180 --- /dev/null +++ b/src/components/borderBox12/style.less @@ -0,0 +1,19 @@ +.dv-border-box-12 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox13/index.js b/src/components/borderBox13/index.js new file mode 100644 index 0000000..db487f8 --- /dev/null +++ b/src/components/borderBox13/index.js @@ -0,0 +1,74 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#6586ec', '#2cf7fe'] + +const BorderBox13 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-13', className), [ + className + ]) + + return ( +
    + + + + + + + + + +
    + {children} +
    +
    + ) +}) + +BorderBox13.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox13 diff --git a/src/components/borderBox13/style.less b/src/components/borderBox13/style.less new file mode 100644 index 0000000..16c3032 --- /dev/null +++ b/src/components/borderBox13/style.less @@ -0,0 +1,19 @@ +.dv-border-box-13 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox2/index.js b/src/components/borderBox2/index.js new file mode 100644 index 0000000..74ff8fd --- /dev/null +++ b/src/components/borderBox2/index.js @@ -0,0 +1,58 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#fff', 'rgba(255, 255, 255, 0.6)'] + +const BorderBox2 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-2', className), [ + className + ]) + + return ( +
    + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox2.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox2 diff --git a/src/components/borderBox2/style.less b/src/components/borderBox2/style.less new file mode 100644 index 0000000..6524d03 --- /dev/null +++ b/src/components/borderBox2/style.less @@ -0,0 +1,24 @@ +.dv-border-box-2 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + + & > polyline { + fill: none; + stroke-width: 1; + } + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox3/index.js b/src/components/borderBox3/index.js new file mode 100644 index 0000000..f428d69 --- /dev/null +++ b/src/components/borderBox3/index.js @@ -0,0 +1,69 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#2862b7', '#2862b7'] + +const BorderBox3 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-3', className), [ + className + ]) + + return ( +
    + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox3.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox3 diff --git a/src/components/borderBox3/style.less b/src/components/borderBox3/style.less new file mode 100644 index 0000000..8449cea --- /dev/null +++ b/src/components/borderBox3/style.less @@ -0,0 +1,31 @@ +.dv-border-box-3 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + + & > polyline { + fill: none; + } + } + + .dv-bb3-line1 { + stroke-width: 3; + } + + .dv-bb3-line2 { + stroke-width: 1; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox4/index.js b/src/components/borderBox4/index.js new file mode 100644 index 0000000..8bb0bad --- /dev/null +++ b/src/components/borderBox4/index.js @@ -0,0 +1,93 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['red', 'rgba(0,0,255,0.8)'] + +const BorderBox4 = forwardRef(({ children, reverse = false, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-4', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox4.propTypes = { + children: PropTypes.node, + reverse: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +// 指定 props 的默认值: +BorderBox.defaultProps = { + reverse: false, + backgroundColor: 'transparent' +} + +export default BorderBox4 diff --git a/src/components/borderBox4/style.less b/src/components/borderBox4/style.less new file mode 100644 index 0000000..0897249 --- /dev/null +++ b/src/components/borderBox4/style.less @@ -0,0 +1,78 @@ +.dv-border-box-4 { + position: relative; + width: 100%; + height: 100%; + + .dv-reverse { + transform: rotate(180deg); + } + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + + & > polyline { + fill: none; + } + } + + .sw1 { + stroke-width: 1; + } + + .sw3 { + stroke-width: 3px; + stroke-linecap: round; + } + + .dv-bb4-line-1 { + .sw1; + } + + .dv-bb4-line-2 { + .sw1; + } + + .dv-bb4-line-3 { + .sw3; + } + + .dv-bb4-line-4 { + .sw3; + } + + .dv-bb4-line-5 { + .sw1; + } + + .dv-bb4-line-6 { + .sw1; + } + + .dv-bb4-line-7 { + .sw1; + } + + .dv-bb4-line-8 { + .sw3; + } + + .dv-bb4-line-9 { + .sw3; + stroke-dasharray: 100 250; + } + + .dv-bb4-line-10 { + .sw1; + stroke-dasharray: 80 270; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox5/index.js b/src/components/borderBox5/index.js new file mode 100644 index 0000000..120965a --- /dev/null +++ b/src/components/borderBox5/index.js @@ -0,0 +1,88 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['rgba(255, 255, 255, 0.35)', 'rgba(255, 255, 255, 0.20)'] + +const BorderBox5 = forwardRef(({ children, reverse = false, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-5', className), [ + className + ]) + + return ( +
    + + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox5.propTypes = { + children: PropTypes.node, + reverse: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +// 指定 props 的默认值: +BorderBox.defaultProps = { + reverse: false, + backgroundColor: 'transparent' +} + +export default BorderBox5 diff --git a/src/components/borderBox5/style.less b/src/components/borderBox5/style.less new file mode 100644 index 0000000..fdb8fdd --- /dev/null +++ b/src/components/borderBox5/style.less @@ -0,0 +1,39 @@ +.dv-border-box-5 { + position: relative; + width: 100%; + height: 100%; + + .dv-reverse { + transform: rotate(180deg); + } + + .dv-border-svg-container { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + + & > polyline { + fill: none; + } + } + + .dv-bb5-line-1, .dv-bb5-line-2 { + stroke-width: 1; + } + + .dv-bb5-line-3, .dv-bb5-line-6 { + stroke-width: 5; + } + + .dv-bb5-line-4, .dv-bb5-line-5 { + stroke-width: 2; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox6/index.js b/src/components/borderBox6/index.js new file mode 100644 index 0000000..bc67990 --- /dev/null +++ b/src/components/borderBox6/index.js @@ -0,0 +1,67 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['rgba(255, 255, 255, 0.35)', 'gray'] + +const BorderBox6 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-6', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox6.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox6 diff --git a/src/components/borderBox6/style.less b/src/components/borderBox6/style.less new file mode 100644 index 0000000..5edfedd --- /dev/null +++ b/src/components/borderBox6/style.less @@ -0,0 +1,24 @@ +.dv-border-box-6 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + + & > polyline { + fill: none; + stroke-width: 1; + } + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox7/index.js b/src/components/borderBox7/index.js new file mode 100644 index 0000000..6804770 --- /dev/null +++ b/src/components/borderBox7/index.js @@ -0,0 +1,83 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['rgba(128,128,128,0.3)', 'rgba(128,128,128,0.5)'] + +const BorderBox7 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-7', className), [ + className + ]) + + const styles = useMemo(() => ({ + boxShadow: `inset 0 0 40px ${mergedColor[0]}`, + border: `1px solid ${mergedColor[0]}`, + backgroundColor, + ...style + }), [style, mergedColor, backgroundColor]) + + return ( +
    + + + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox7.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox7 diff --git a/src/components/borderBox7/style.less b/src/components/borderBox7/style.less new file mode 100644 index 0000000..1396a81 --- /dev/null +++ b/src/components/borderBox7/style.less @@ -0,0 +1,32 @@ +.dv-border-box-7 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + + & > polyline { + fill: none; + stroke-linecap: round; + } + } + + .dv-bb7-line-width-2 { + stroke-width: 2; + } + + .dv-bb7-line-width-5 { + stroke-width: 5; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} diff --git a/src/components/borderBox8/index.js b/src/components/borderBox8/index.js new file mode 100644 index 0000000..4064994 --- /dev/null +++ b/src/components/borderBox8/index.js @@ -0,0 +1,105 @@ +import React, { useState, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['#235fa7', '#4fd2dd'] + +const BorderBox8 = forwardRef(( + { + children, + className, + style, + color = [], + dur = 3, + backgroundColor = 'transparent', + reverse = false + }, + ref +) => { + const { width, height, domRef } = useAutoResize(ref) + + const [{ path, gradient, mask }] = useState(() => { + const id = uuid() + + return { + path: `border-box-8-path-${id}`, + gradient: `border-box-8-gradient-${id}`, + mask: `border-box-8-mask-${id}` + } + }) + + const pathD = useMemo(() => + reverse + ? `M 2.5, 2.5 L 2.5, ${height - 2.5} L ${width - 2.5}, ${height - 2.5} L ${width - 2.5}, 2.5 L 2.5, 2.5` + : `M2.5, 2.5 L${width - 2.5}, 2.5 L${width - 2.5}, ${height - 2.5} L2.5, ${height - 2.5} L2.5, 2.5` + , [width, height, reverse]) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const length = useMemo(() => (width + height - 5) * 2, [width, height]) + + const classNames = useMemo(() => classnames('dv-border-box-8', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox8.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + dur: PropTypes.number, + backgroundColor: PropTypes.string, + reverse: PropTypes.bool +} + +export default BorderBox8 diff --git a/src/components/borderBox8/style.less b/src/components/borderBox8/style.less new file mode 100644 index 0000000..262a539 --- /dev/null +++ b/src/components/borderBox8/style.less @@ -0,0 +1,19 @@ +.dv-border-box-8 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + left: 0px; + top: 0px; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/borderBox9/index.js b/src/components/borderBox9/index.js new file mode 100644 index 0000000..d0a3f5a --- /dev/null +++ b/src/components/borderBox9/index.js @@ -0,0 +1,176 @@ +import React, { useState, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['#11eefd', '#0078d2'] + +const BorderBox9 = forwardRef(({ children, className, style, color = [], backgroundColor = 'transparent' }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const [{ gradientId, maskId }] = useState(() => { + const id = uuid() + + return { + gradientId: `border-box-9-gradient-${id}`, + maskId: `border-box-9-mask-${id}` + } + }) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-border-box-9', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    {children}
    +
    + ) +}) + +BorderBox9.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + backgroundColor: PropTypes.string +} + +export default BorderBox9 diff --git a/src/components/borderBox9/style.less b/src/components/borderBox9/style.less new file mode 100644 index 0000000..4d908c9 --- /dev/null +++ b/src/components/borderBox9/style.less @@ -0,0 +1,19 @@ +.dv-border-box-9 { + position: relative; + width: 100%; + height: 100%; + + .dv-border-svg-container { + position: absolute; + width: 100%; + height: 100%; + left: 0px; + top: 0px; + } + + .border-box-content { + position: relative; + width: 100%; + height: 100%; + } +} \ No newline at end of file diff --git a/src/components/capsuleChart/index.js b/src/components/capsuleChart/index.js new file mode 100644 index 0000000..581f795 --- /dev/null +++ b/src/components/capsuleChart/index.js @@ -0,0 +1,137 @@ +import React, { useEffect, useState, useMemo } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' + + +import './style.less' + +const defaultConfig = { + /** + * @description Capsule chart data + * @type {Array} + * @default data = [] + * @example data = [{ name: 'foo1', value: 100 }, { name: 'foo2', value: 100 }] + */ + data: [], + /** + * @description Colors (hex|rgb|rgba|color keywords) + * @type {Array} + * @default color = ['#37a2da', '#32c5e9', '#67e0e3', '#9fe6b8', '#ffdb5c', '#ff9f7f', '#fb7293'] + * @example color = ['#000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)', 'red'] + */ + colors: [ + '#37a2da', + '#32c5e9', + '#67e0e3', + '#9fe6b8', + '#ffdb5c', + '#ff9f7f', + '#fb7293' + ], + /** + * @description Chart unit + * @type {String} + * @default unit = '' + */ + unit: '', + /** + * @description Show item value + * @type {Boolean} + * @default showValue = false + */ + showValue: false +} + +const CapsuleChart = ({ config = {}, className, style }) => { + const [{ mergedConfig, labelData, capsuleLength, capsuleValue }, setState] = useState({ + mergedConfig: null, + labelData: [], + capsuleLength: [], + capsuleValue: [] + }) + + useEffect(() => { + const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {}) + + const { data } = mergedConfig + + if (!data.length) return + + const capsuleValue = data.map(({ value }) => value) + + const maxValue = Math.max(...capsuleValue) + + const oneFifth = maxValue / 5 + + setState({ + mergedConfig, + capsuleValue, + capsuleLength: capsuleValue.map(v => (maxValue ? v / maxValue : 0)), + labelData: [...new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth)))] + }) + }, [config]) + + const classNames = useMemo(() => classnames('dv-capsule-chart', className), [ + className + ]) + + return ( +
    + {!!mergedConfig && ( + +
    + {mergedConfig.data.map(({ name }) => ( +
    {name}
    + ))} +
     
    +
    + +
    + {capsuleLength.map((capsule, index) => ( +
    +
    + { + mergedConfig.showValue && +
    + { capsuleValue[index] } +
    + } +
    +
    + ))} + +
    + {labelData.map((label, index) => ( +
    {label}
    + ))} +
    +
    + + {!!mergedConfig.unit && ( +
    {mergedConfig.unit}
    + )} +
    + )} +
    + ) +} + +CapsuleChart.propTypes = { + config: PropTypes.object, + className: PropTypes.string, + style: PropTypes.object +} + +export default CapsuleChart diff --git a/src/components/capsuleChart/style.less b/src/components/capsuleChart/style.less new file mode 100644 index 0000000..f8ab54d --- /dev/null +++ b/src/components/capsuleChart/style.less @@ -0,0 +1,71 @@ +.dv-capsule-chart { + position: relative; + display: flex; + flex-direction: row; + box-sizing: border-box; + padding: 10px; + color: #fff; + + .label-column { + display: flex; + flex-direction: column; + justify-content: space-between; + box-sizing: border-box; + padding-right: 10px; + text-align: right; + font-size: 12px; + + div { + height: 20px; + line-height: 20px; + } + } + + .capsule-container { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .capsule-item { + box-shadow: 0 0 3px #999; + height: 10px; + margin: 5px 0px; + border-radius: 5px; + + .capsule-item-column { + position: relative; + height: 8px; + margin-top: 1px; + border-radius: 5px; + transition: all 0.3s; + display: flex; + justify-content: flex-end; + align-items: center; + + .capsule-item-value { + font-size: 12px; + transform: translateX(100%); + } + } + } + + .unit-label { + height: 20px; + font-size: 12px; + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + } + + .unit-text { + text-align: right; + display: flex; + align-items: flex-end; + font-size: 12px; + line-height: 20px; + margin-left: 10px; + } +} \ No newline at end of file diff --git a/src/components/conicalColumnChart/index.js b/src/components/conicalColumnChart/index.js new file mode 100644 index 0000000..6c30b1b --- /dev/null +++ b/src/components/conicalColumnChart/index.js @@ -0,0 +1,183 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultConfig = { + /** + * @description Chart data + * @type {Array} + * @default data = [] + */ + data: [], + /** + * @description Chart img + * @type {Array} + * @default img = [] + */ + img: [], + /** + * @description Chart font size + * @type {Number} + * @default fontSize = 12 + */ + fontSize: 12, + /** + * @description Img side length + * @type {Number} + * @default imgSideLength = 30 + */ + imgSideLength: 30, + /** + * @description Column color + * @type {String} + * @default columnColor = 'rgba(0, 194, 255, 0.4)' + */ + columnColor: 'rgba(0, 194, 255, 0.4)', + /** + * @description Text color + * @type {String} + * @default textColor = '#fff' + */ + textColor: '#fff', + /** + * @description Show value + * @type {Boolean} + * @default showValue = false + */ + showValue: false +} + +function getData(mergedConfig) { + let { data } = mergedConfig + + data = deepClone(data, true) + + data.sort(({ value: a }, { value: b }) => { + if (a > b) return -1 + if (a < b) return 1 + if (a === b) return 0 + }) + + const max = data[0] ? data[0].value : 10 + + data = data.map(item => ({ + ...item, + percent: item.value / max + })) + + return data +} + +const ConicalColumnChart = forwardRef(({ config = {}, className, style }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const { mergedConfig, column } = useMemo(calcData, [config, width, height]) + + function calcData() { + const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {}) + + mergedConfig.data = getData(mergedConfig) + + return { mergedConfig, column: getColumn(mergedConfig) } + } + + function getColumn(mergedConfig) { + const { imgSideLength, fontSize, data } = mergedConfig + + const itemNum = data.length + const gap = width / (itemNum + 1) + + const useAbleHeight = height - imgSideLength - fontSize - 5 + const svgBottom = height - fontSize - 5 + + return data.map((item, i) => { + const { percent } = item + + const middleXPos = gap * (i + 1) + const leftXPos = gap * i + const rightXpos = gap * (i + 2) + + const middleYPos = svgBottom - useAbleHeight * percent + const controlYPos = useAbleHeight * percent * 0.6 + middleYPos + + const d = ` + M${leftXPos}, ${svgBottom} + Q${middleXPos}, ${controlYPos} ${middleXPos},${middleYPos} + M${middleXPos},${middleYPos} + Q${middleXPos}, ${controlYPos} ${rightXpos},${svgBottom} + L${leftXPos}, ${svgBottom} + Z + ` + + const textY = (svgBottom + middleYPos) / 2 + fontSize / 2 + + return { + ...item, + d, + x: middleXPos, + y: middleYPos, + textY + } + }) + } + + const classNames = useMemo( + () => classnames('dv-conical-column-chart', className), + [className] + ) + + return ( +
    + + {column.map((item, i) => ( + + + + {item.name} + + {!!mergedConfig.img.length && ( + + )} + {mergedConfig.showValue && ( + + {item.value} + + )} + + ))} + +
    + ) +}) + +ConicalColumnChart.propTypes = { + config: PropTypes.object, + className: PropTypes.string, + style: PropTypes.object +} + +export default ConicalColumnChart diff --git a/src/components/conicalColumnChart/style.less b/src/components/conicalColumnChart/style.less new file mode 100644 index 0000000..933d12d --- /dev/null +++ b/src/components/conicalColumnChart/style.less @@ -0,0 +1,8 @@ +.dv-conical-column-chart { + width: 100%; + height: 100%; + + text { + text-anchor: middle; + } +} \ No newline at end of file diff --git a/src/components/decoration1/index.js b/src/components/decoration1/index.js new file mode 100644 index 0000000..eb266d3 --- /dev/null +++ b/src/components/decoration1/index.js @@ -0,0 +1,162 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#fff', '#0de7c2'] + +const pointSideLength = 2.5 + +const halfPointSideLength = pointSideLength / 2 + +const svgWH = [200, 50] + +const rowNum = 4 + +const rowPoints = 20 + +function getPoints() { + const [w, h] = svgWH + + const horizontalGap = w / (rowPoints + 1) + const verticalGap = h / (rowNum + 1) + + const points = new Array(rowNum) + .fill(0) + .map((foo, i) => + new Array(rowPoints) + .fill(0) + .map((foo, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)]) + ) + + return points.reduce((all, item) => [...all, ...item], []) +} + +const Decoration1 = forwardRef(({ className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + function calcSVGData() { + const points = getPoints() + + return { + points, + rects: [points[rowPoints * 2 - 1], points[rowPoints * 2 - 3]], + svgScale: [width / svgWH[0], height / svgWH[1]] + } + } + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const { svgScale, points, rects } = useMemo(calcSVGData, [width, height]) + + const classNames = useMemo(() => classnames('dv-decoration-1', className), [ + className + ]) + + return ( +
    + + {points.reduce((prev, point, i) => { + return Math.random() > 0.6 + ? [ + ...prev, + + {Math.random() > 0.6 && ( + + )} + + ] + : prev + }, [])} + {!!rects[0] && ( + + + + + + + )} + {!!rects[1] && ( + + + + + )} + +
    + ) +}) + +Decoration1.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration1 diff --git a/src/components/decoration1/style.less b/src/components/decoration1/style.less new file mode 100644 index 0000000..4aaf2b1 --- /dev/null +++ b/src/components/decoration1/style.less @@ -0,0 +1,8 @@ +.dv-decoration-1 { + width: 100%; + height: 100%; + + svg { + transform-origin: left top; + } +} \ No newline at end of file diff --git a/src/components/decoration10/index.js b/src/components/decoration10/index.js new file mode 100644 index 0000000..7706db3 --- /dev/null +++ b/src/components/decoration10/index.js @@ -0,0 +1,198 @@ +import React, { useRef, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['#00c2ff', 'rgba(0, 194, 255, 0.3)'] + +const Decoration10 = forwardRef(({ className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const { + animationId1, + animationId2, + animationId3, + animationId4, + animationId5, + animationId6, + animationId7 + } = useRef({ + animationId1: `d10ani1${uuid()}`, + animationId2: `d10ani2${uuid()}`, + animationId3: `d10ani3${uuid()}`, + animationId4: `d10ani4${uuid()}`, + animationId5: `d10ani5${uuid()}`, + animationId6: `d10ani6${uuid()}`, + animationId7: `d10ani7${uuid()}` + }).current + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-decoration-10', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + ) +}) + +Decoration10.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration10 diff --git a/src/components/decoration10/style.less b/src/components/decoration10/style.less new file mode 100644 index 0000000..8281aa3 --- /dev/null +++ b/src/components/decoration10/style.less @@ -0,0 +1,5 @@ +.dv-decoration-10 { + width: 100%; + height: 100%; + display: flex; +} \ No newline at end of file diff --git a/src/components/decoration11/index.js b/src/components/decoration11/index.js new file mode 100644 index 0000000..6811b27 --- /dev/null +++ b/src/components/decoration11/index.js @@ -0,0 +1,87 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { fade, deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#1a98fc', '#2cf7fe'] + +const Decoration11 = forwardRef(({ children, className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-decoration-11', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + + +
    + {children} +
    +
    + ) +}) + +Decoration11.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration11 diff --git a/src/components/decoration11/style.less b/src/components/decoration11/style.less new file mode 100644 index 0000000..200d3dc --- /dev/null +++ b/src/components/decoration11/style.less @@ -0,0 +1,17 @@ +.dv-decoration-11 { + position: relative; + width: 100%; + height: 100%; + display: flex; + + .decoration-content { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } +} \ No newline at end of file diff --git a/src/components/decoration12/index.js b/src/components/decoration12/index.js new file mode 100644 index 0000000..b2c8159 --- /dev/null +++ b/src/components/decoration12/index.js @@ -0,0 +1,224 @@ +import React, { useMemo, forwardRef, useRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + + + +import { fade, getCircleRadianPoint , deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['#2783ce', '#2cf7fe'] + +const segment = 30 + +const sectorAngle = Math.PI / 3 + +const ringNum = 3 + +const ringWidth = 1 + +const showSplitLine = true + +const Decoration12 = forwardRef(({ children, className, style, color = [], scanDur = 3, haloDur = 2 }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const x = width / 2 + + const y = height / 2 + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const pathD = useMemo(() => { + const startAngle = -Math.PI / 2 + const angleGap = sectorAngle / segment + const r = width / 4 + let lastEndPoints = getCircleRadianPoint(x, y, r, startAngle) + + return new Array(segment) + .fill('') + .map((_, i) => { + const endPoints = getCircleRadianPoint(x, y, r, startAngle - (i + 1) * angleGap).map(_ => _.toFixed(5)) + const d = `M${lastEndPoints.join(',')} A${r}, ${r} 0 0 0 ${endPoints.join(',')}` + lastEndPoints = endPoints + return d + }) + }, [x, y, width]) + + const pathColor = useMemo(() => { + const color = mergedColor[0] + const colorGap = 100 / (segment - 1) + + return new Array(segment) + .fill(color) + .map((_, i) => fade(color, 100 - i * colorGap)) + }, [mergedColor]) + + const circleR = useMemo(() => { + const radiusGap = (width / 2 - ringWidth / 2) / ringNum + + return new Array(ringNum) + .fill(0) + .map((_, i) => radiusGap * (i + 1)) + }, [width]) + + const splitLinePoints = useMemo(() => { + const angleGap = Math.PI / 6 + const r = width / 2 + return new Array(6) + .fill('') + .map((_, i) => { + const startAngle = angleGap * (i + 1) + const endAngle = startAngle + Math.PI + const startPoint = getCircleRadianPoint(x, y, r, startAngle) + const endPoint = getCircleRadianPoint(x, y, r, endAngle) + return `${startPoint.join(',')} ${endPoint.join(',')}` + }) + }, [x, y, width]) + + const arcD = useMemo(() => { + const angleGap = Math.PI / 6 + const r = width / 2 - 1 + + return new Array(4) + .fill('') + .map((_, i) => { + const startAngle = angleGap * (3 * i + 1) + const endAngle = startAngle + angleGap + const startPoint = getCircleRadianPoint(x, y, r, startAngle) + const endPoint = getCircleRadianPoint(x, y, r, endAngle) + return `M${startPoint.join(',')} A${x}, ${y} 0 0 1 ${endPoint.join(',')}` + }) + }, [x, y, width]) + + const idRef = useRef({ + gId: `decoration-12-g-${uuid()}`, + gradientId: `decoration-12-gradient-${uuid()}` + }) + + const classNames = useMemo(() => classnames('dv-decoration-12', className), [ + className + ]) + + return ( +
    + + + + { + pathD.map((d, i) => ) + } + + + + + + + + + { + circleR.map(r => ) + } + + + + + + + + + { + showSplitLine && + { + splitLinePoints.map(p => ) + } + + + } + + { + arcD.map(d => ) + } + + + + + + +
    + {children} +
    +
    + ) +}) + +Decoration12.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + scanDur: PropTypes.number, + haloDur: PropTypes.number +} + +export default Decoration12 diff --git a/src/components/decoration12/style.less b/src/components/decoration12/style.less new file mode 100644 index 0000000..fbf60a5 --- /dev/null +++ b/src/components/decoration12/style.less @@ -0,0 +1,17 @@ +.dv-decoration-12 { + position: relative; + width: 100%; + height: 100%; + display: flex; + + .decoration-content { + position: absolute; + top: 0px; + left: 0px; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } +} \ No newline at end of file diff --git a/src/components/decoration2/index.js b/src/components/decoration2/index.js new file mode 100644 index 0000000..d233031 --- /dev/null +++ b/src/components/decoration2/index.js @@ -0,0 +1,79 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#3faacb', '#fff'] + +const Decoration2 = forwardRef(({ reverse = false, dur = 6, className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + function calcSVGData() { + return reverse + ? { w: 1, h: height, x: width / 2, y: 0 } + : { w: width, h: 1, x: 0, y: height / 2 } + } + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const { x, y, w, h } = useMemo(calcSVGData, [reverse, width, height]) + + const classNames = useMemo(() => classnames('dv-decoration-2', className), [ + className + ]) + + return ( +
    + + + + + + + + + +
    + ) +}) + +Decoration2.propTypes = { + dur: PropTypes.number, + reverse: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +// 指定 props 的默认值: +Decoration2.defaultProps = { + reverse: false, + dur: 6 +} + +export default Decoration2 diff --git a/src/components/decoration2/style.less b/src/components/decoration2/style.less new file mode 100644 index 0000000..11455c8 --- /dev/null +++ b/src/components/decoration2/style.less @@ -0,0 +1,7 @@ +.dv-decoration-2 { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/src/components/decoration3/index.js b/src/components/decoration3/index.js new file mode 100644 index 0000000..842d728 --- /dev/null +++ b/src/components/decoration3/index.js @@ -0,0 +1,98 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#7acaec', 'transparent'] + +const pointSideLength = 7 + +const svgWH = [300, 35] + +const rowNum = 2 + +const rowPoints = 25 + +const halfPointSideLength = pointSideLength / 2 + +function getPoints() { + const [w, h] = svgWH + + const horizontalGap = w / (rowPoints + 1) + const verticalGap = h / (rowNum + 1) + + let points = new Array(rowNum) + .fill(0) + .map((foo, i) => + new Array(rowPoints) + .fill(0) + .map((foo, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)]) + ) + + return points.reduce((all, item) => [...all, ...item], []) +} + +const Decoration3 = forwardRef(({ className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + function calcSVGData() { + return { + points: getPoints(), + svgScale: [width / svgWH[0], height / svgWH[1]] + } + } + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const { svgScale, points } = useMemo(calcSVGData, [width, height]) + + const classNames = useMemo(() => classnames('dv-decoration-3', className), [ + className + ]) + + return ( +
    + + {points.map((point, i) => ( + + {Math.random() > 0.6 && ( + + )} + + ))} + +
    + ) +}) + +Decoration3.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration3 diff --git a/src/components/decoration3/style.less b/src/components/decoration3/style.less new file mode 100644 index 0000000..3d71199 --- /dev/null +++ b/src/components/decoration3/style.less @@ -0,0 +1,8 @@ +.dv-decoration-3 { + width: 100%; + height: 100%; + + svg { + transform-origin: left top; + } +} \ No newline at end of file diff --git a/src/components/decoration4/index.js b/src/components/decoration4/index.js new file mode 100644 index 0000000..d3268e0 --- /dev/null +++ b/src/components/decoration4/index.js @@ -0,0 +1,67 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.3)'] + +const Decoration4 = forwardRef(({ reverse = false, dur = 3, className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-decoration-4', className), [ + className + ]) + + return ( +
    +
    + + + + +
    +
    + ) +}) + +Decoration4.propTypes = { + dur: PropTypes.number, + reverse: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +// 指定 props 的默认值: +Decoration4.defaultProps = { + reverse: false, + dur: 3 +} + +export default Decoration4 diff --git a/src/components/decoration4/style.less b/src/components/decoration4/style.less new file mode 100644 index 0000000..3d6eee8 --- /dev/null +++ b/src/components/decoration4/style.less @@ -0,0 +1,52 @@ +.dv-decoration-4 { + position: relative; + width: 100%; + height: 100%; + + .container { + display: flex; + overflow: hidden; + position: absolute; + flex: 1; + } + + .normal { + animation: ani-height ease-in-out infinite; + left: 50%; + margin-left: -2px; + } + + .reverse { + animation: ani-width ease-in-out infinite; + top: 50%; + margin-top: -2px; + } + + @keyframes ani-height { + 0% { + height: 0%; + } + + 70% { + height: 100%; + } + + 100% { + height: 100%; + } + } + + @keyframes ani-width { + 0% { + width: 0%; + } + + 70% { + width: 100%; + } + + 100% { + width: 100%; + } + } +} diff --git a/src/components/decoration5/index.js b/src/components/decoration5/index.js new file mode 100644 index 0000000..350abc8 --- /dev/null +++ b/src/components/decoration5/index.js @@ -0,0 +1,107 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { getPolylineLength, deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#3f96a5', '#3f96a5'] + +const Decoration5 = forwardRef(({ className, dur = 1.2, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + function calcSVGData() { + let line1Points = [ + [0, height * 0.2], + [width * 0.18, height * 0.2], + [width * 0.2, height * 0.4], + [width * 0.25, height * 0.4], + [width * 0.27, height * 0.6], + [width * 0.72, height * 0.6], + [width * 0.75, height * 0.4], + [width * 0.8, height * 0.4], + [width * 0.82, height * 0.2], + [width, height * 0.2] + ] + + let line2Points = [[width * 0.3, height * 0.8], [width * 0.7, height * 0.8]] + + const line1Length = getPolylineLength(line1Points) + const line2Length = getPolylineLength(line2Points) + + line1Points = line1Points.map(point => point.join(',')).join(' ') + line2Points = line2Points.map(point => point.join(',')).join(' ') + + return { line1Points, line2Points, line1Length, line2Length } + } + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const { line1Points, line2Points, line1Length, line2Length } = useMemo( + calcSVGData, + [width, height] + ) + + const classNames = useMemo(() => classnames('dv-decoration-5', className), [ + className + ]) + + return ( +
    + + + + + + + + +
    + ) +}) + +Decoration5.propTypes = { + dur: PropTypes.number, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration5 diff --git a/src/components/decoration5/style.less b/src/components/decoration5/style.less new file mode 100644 index 0000000..129b203 --- /dev/null +++ b/src/components/decoration5/style.less @@ -0,0 +1,4 @@ +.dv-decoration-5 { + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/components/decoration6/index.js b/src/components/decoration6/index.js new file mode 100644 index 0000000..c2825f6 --- /dev/null +++ b/src/components/decoration6/index.js @@ -0,0 +1,136 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import { randomExtend } from '../../util' + +import './style.less' + +const defaultColor = ['#7acaec', '#7acaec'] + +const svgWH = [300, 35] + +const rowNum = 1 +const rowPoints = 40 + +const rectWidth = 7 +const halfRectWidth = rectWidth / 2 + +function getPoints() { + const [w, h] = svgWH + + const horizontalGap = w / (rowPoints + 1) + const verticalGap = h / (rowNum + 1) + + let points = new Array(rowNum) + .fill(0) + .map((foo, i) => + new Array(rowPoints) + .fill(0) + .map((foo, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)]) + ) + + return points.reduce((all, item) => [...all, ...item], []) +} + +function getData() { + const [, h] = svgWH + + const heights = new Array(rowNum * rowPoints) + .fill(0) + .map(foo => + Math.random() > 0.8 + ? randomExtend(0.7 * h, h) + : randomExtend(0.2 * h, 0.5 * h) + ) + + const minHeights = new Array(rowNum * rowPoints) + .fill(0) + .map((foo, i) => heights[i] * Math.random()) + + const randoms = new Array(rowNum * rowPoints) + .fill(0) + .map(foo => Math.random() + 1.5) + + return { heights, minHeights, randoms } +} + +const Decoration6 = forwardRef(({ className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + function calcSVGData() { + return { + ...getData(), + points: getPoints(), + svgScale: [width / svgWH[0], height / svgWH[1]] + } + } + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const { points, heights, minHeights, randoms, svgScale } = useMemo( + calcSVGData, + [width, height] + ) + + const classNames = useMemo(() => classnames('dv-decoration-6', className), [ + [className] + ]) + + return ( +
    + + {points.map((point, i) => ( + 0.5 ? 0 : 1]} + x={point[0] - halfRectWidth} + y={point[1] - heights[i] / 2} + width={rectWidth} + height={heights[i]} + > + + + + ))} + +
    + ) +}) + +Decoration6.propTypes = { + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration6 diff --git a/src/components/decoration6/style.less b/src/components/decoration6/style.less new file mode 100644 index 0000000..f4dcd1a --- /dev/null +++ b/src/components/decoration6/style.less @@ -0,0 +1,8 @@ +.dv-decoration-6 { + width: 100%; + height: 100%; + + svg { + transform-origin: left top; + } +} \ No newline at end of file diff --git a/src/components/decoration7/index.js b/src/components/decoration7/index.js new file mode 100644 index 0000000..8e5c440 --- /dev/null +++ b/src/components/decoration7/index.js @@ -0,0 +1,63 @@ +import React, { useMemo } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' + + +import './style.less' + +const defaultColor = ['#1dc1f5', '#1dc1f5'] + +const Decoration7 = ({ children, className, style, color = [] }) => { + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const classNames = useMemo(() => classnames('dv-decoration-7', className), [ + className + ]) + + return ( +
    + + + + + {children} + + + + +
    + ) +} + +Decoration7.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +export default Decoration7 diff --git a/src/components/decoration7/style.less b/src/components/decoration7/style.less new file mode 100644 index 0000000..3882e22 --- /dev/null +++ b/src/components/decoration7/style.less @@ -0,0 +1,7 @@ +.dv-decoration-7 { + display: flex; + width: 100%; + height: 100%; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/src/components/decoration8/index.js b/src/components/decoration8/index.js new file mode 100644 index 0000000..2a45797 --- /dev/null +++ b/src/components/decoration8/index.js @@ -0,0 +1,75 @@ +import React, { useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const defaultColor = ['#3f96a5', '#3f96a5'] + +const Decoration8 = forwardRef(({ reverse = false, className, style, color = [] }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const xPos = pos => (!reverse ? pos : width - pos) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const [pointsOne, pointsTwo, pointsThree] = useMemo( + () => [ + `${xPos(0)}, 0 ${xPos(30)}, ${height / 2}`, + `${xPos(20)}, 0 ${xPos(50)}, ${height / 2} ${xPos(width)}, ${height / 2}`, + `${xPos(0)}, ${height - 3}, ${xPos(200)}, ${height - 3}` + ], + [reverse, width, height] + ) + + const classNames = useMemo(() => classnames('dv-decoration-8', className), [ + className + ]) + + return ( +
    + + + + + + + +
    + ) +}) + +Decoration8.propTypes = { + reverse: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array +} + +// 指定 props 的默认值: +Decoration8.defaultProps = { + reverse: false +} + +export default Decoration8 diff --git a/src/components/decoration8/style.less b/src/components/decoration8/style.less new file mode 100644 index 0000000..9943cf5 --- /dev/null +++ b/src/components/decoration8/style.less @@ -0,0 +1,5 @@ +.dv-decoration-8 { + display: flex; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/components/decoration9/index.js b/src/components/decoration9/index.js new file mode 100644 index 0000000..ae4fc90 --- /dev/null +++ b/src/components/decoration9/index.js @@ -0,0 +1,137 @@ +import React, { useRef, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { fade, deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultColor = ['rgba(3, 166, 224, 0.8)', 'rgba(3, 166, 224, 0.5)'] + +const svgWH = [100, 100] + +const Decoration9 = forwardRef(({ children, className, style, color = [], dur = 3 }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const polygonIdRef = useRef(`decoration-9-polygon-${uuid()}`) + + const mergedColor = useMemo(() => deepMerge(deepClone(defaultColor, true), color || []), [color]) + + const svgScale = useMemo(() => { + const [w, h] = svgWH + + return [width / w, height / h] + }, [width, height]) + + const classNames = useMemo(() => classnames('dv-decoration-9', className), [ + className + ]) + + return ( +
    + + + + + + + + + + + + + + + {new Array(20).fill(0).map((foo, i) => ( + 0.4 ? 'transparent' : mergedColor[0] + } + > + + + ))} + + + + + {children} +
    + ) +}) + +Decoration9.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object, + color: PropTypes.array, + dur: PropTypes.number +} + +export default Decoration9 diff --git a/src/components/decoration9/style.less b/src/components/decoration9/style.less new file mode 100644 index 0000000..a91cf83 --- /dev/null +++ b/src/components/decoration9/style.less @@ -0,0 +1,15 @@ +.dv-decoration-9 { + position: relative; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + + svg { + position: absolute; + left: 0px; + top: 0px; + transform-origin: left top; + } +} \ No newline at end of file diff --git a/src/components/flylineChart/index.js b/src/components/flylineChart/index.js new file mode 100644 index 0000000..6f0336a --- /dev/null +++ b/src/components/flylineChart/index.js @@ -0,0 +1,454 @@ +import React, { useEffect, useState, useRef, useCallback, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { randomExtend, getPointDistance,uuid } from '../../datav/util' + +import './style.less' + +const defaultConfig = { + /** + * @description Flyline chart center point + * @type {Array} + * @default centerPoint = [0, 0] + */ + centerPoint: [0, 0], + /** + * @description Flyline start points + * @type {Array>} + * @default points = [] + * @example points = [[10, 10], [100, 100]] + */ + points: [], + /** + * @description Flyline width + * @type {Number} + * @default lineWidth = 1 + */ + lineWidth: 1, + /** + * @description Orbit color + * @type {String} + * @default orbitColor = 'rgba(103, 224, 227, .2)' + */ + orbitColor: 'rgba(103, 224, 227, .2)', + /** + * @description Flyline color + * @type {String} + * @default orbitColor = '#ffde93' + */ + flylineColor: '#ffde93', + /** + * @description K value + * @type {Number} + * @default k = -0.5 + * @example k = -1 ~ 1 + */ + k: -0.5, + /** + * @description Flyline curvature + * @type {Number} + * @default curvature = 5 + */ + curvature: 5, + /** + * @description Flyline radius + * @type {Number} + * @default flylineRadius = 100 + */ + flylineRadius: 100, + /** + * @description Flyline animation duration + * @type {Array} + * @default duration = [20, 30] + */ + duration: [20, 30], + /** + * @description Relative points position + * @type {Boolean} + * @default relative = true + */ + relative: true, + /** + * @description Back ground image url + * @type {String} + * @default bgImgUrl = '' + * @example bgImgUrl = './img/bg.jpg' + */ + bgImgUrl: '', + /** + * @description Text configuration + * @type {Object} + */ + text: { + /** + * @description Text offset + * @type {Array} + * @default offset = [0, 15] + */ + offset: [0, 15], + /** + * @description Text color + * @type {String} + * @default color = '#ffdb5c' + */ + color: '#ffdb5c', + /** + * @description Text font size + * @type {Number} + * @default fontSize = 12 + */ + fontSize: 12 + }, + /** + * @description Halo configuration + * @type {Object} + */ + halo: { + /** + * @description Weather to show halo + * @type {Boolean} + * @default show = true + * @example show = true | false + */ + show: true, + /** + * @description Halo animation duration (10 = 1s) + * @type {Number} + * @default duration = 30 + */ + duration: 30, + /** + * @description Halo color + * @type {String} + * @default color = '#fb7293' + */ + color: '#fb7293', + /** + * @description Halo max radius + * @type {Number} + * @default radius = 120 + */ + radius: 120 + }, + /** + * @description Center point img configuration + * @type {Object} + */ + centerPointImg: { + /** + * @description Center point img width + * @type {Number} + * @default width = 40 + */ + width: 40, + /** + * @description Center point img height + * @type {Number} + * @default height = 40 + */ + height: 40, + /** + * @description Center point img url + * @type {String} + * @default url = '' + */ + url: '' + }, + /** + * @description Points img configuration + * @type {Object} + * @default radius = 120 + */ + pointsImg: { + /** + * @description Points img width + * @type {Number} + * @default width = 15 + */ + width: 15, + /** + * @description Points img height + * @type {Number} + * @default height = 15 + */ + height: 15, + /** + * @description Points img url + * @type {String} + * @default url = '' + */ + url: '' + } +} + +function getControlPoint([sx, sy], [ex, ey], { curvature, k }) { + const [mx, my] = [(sx + ex) / 2, (sy + ey) / 2] + + const distance = getPointDistance([sx, sy], [ex, ey]) + + const targetLength = distance / curvature + const disDived = targetLength / 2 + + let [dx, dy] = [mx, my] + + do { + dx += disDived + dy = my - k * mx + k * dx + } while (getPointDistance([mx, my], [dx, dy]) < targetLength) + + return [dx, dy] +} + +const FlyLineChart = forwardRef(({ config = {}, dev = false, className, style }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const { unique, gradientId, gradient2Id } = useRef({ + unique: Math.random(), + gradientId: `gradient-id-${uuid()}`, + gradient2Id: `gradient2-id-${uuid()}` + }).current + + const { mergedConfig, paths, times, texts } = useMemo(calcData, [ + config, + width, + height + ]) + + const [lengths, setLengths] = useState([]) + + const pathDomRef = useRef([]) + + function calcData() { + const mergedConfig = getMergedConfig() + + const paths = createFlylinePaths(mergedConfig) + + const { duration, points } = mergedConfig + + const times = points.map(foo => randomExtend(...duration) / 10) + + const texts = points.map(({ text }) => text) + + return { mergedConfig, paths, times, texts } + } + + function getMergedConfig() { + const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {}) + + mergedConfig.points = mergedConfig.points.map(item => { + if (Array.isArray(item)) { + return { position: item, text: '' } + } + + return item + }) + + return mergedConfig + } + + function createFlylinePaths(mergedConfig) { + let { centerPoint, relative } = mergedConfig + let points = mergedConfig.points.map(({ position }) => position) + + if (relative) { + centerPoint = [width * centerPoint[0], height * centerPoint[1]] + points = points.map(([x, y]) => [width * x, height * y]) + } + + return points.map(point => { + const controlPoint = getControlPoint(centerPoint, point, mergedConfig) + + return [point, controlPoint, centerPoint] + }) + } + + const consoleClickPos = useCallback( + ({ offsetX, offsetY }) => { + if (!dev) return + + const relativeX = (offsetX / width).toFixed(2) + const relativeY = (offsetY / height).toFixed(2) + + console.warn( + `dv-flyline-chart DEV: \n Click Position is [${offsetX}, ${offsetY}] \n Relative Position is [${relativeX}, ${relativeY}]` + ) + }, + [width, height, dev] + ) + + useEffect(() => { + const lengths = paths.map((foo, i) => + pathDomRef.current[i].getTotalLength() + ) + + setLengths(lengths) + }, [paths]) + + const classNames = useMemo(() => classnames('dv-flyline-chart', className), [ + className + ]) + + return ( +
    + {!!mergedConfig && ( + + + + + + + + + + + + + {!!paths[0] && ( + + + + + )} + + + {!!paths[0] && ( + + )} + + + {!!paths[0] && ( + + )} + + + {!!paths[0] && mergedConfig.halo.show && ( + + )} + + {paths.map((path, i) => ( + + + (pathDomRef.current[i] = e)} + d={`M${path[0].toString()} Q${path[1].toString()} ${path[2].toString()}`} + fill='transparent' + /> + + + + + {lengths[i] && ( + + + + )} + + + + + + + + + + + {texts[i]} + + + ))} + + )} +
    + ) +}) + +FlyLineChart.propTypes = { + config: PropTypes.object, + dev: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object +} + +// 指定 props 的默认值: +FlyLineChart.defaultProps = { + dev: false +} + +export default FlyLineChart diff --git a/src/components/flylineChart/style.less b/src/components/flylineChart/style.less new file mode 100644 index 0000000..fefc38b --- /dev/null +++ b/src/components/flylineChart/style.less @@ -0,0 +1,14 @@ +.dv-flyline-chart { + display: flex; + flex-direction: column; + background-size: 100% 100%; + + polyline { + transition: all 0.3s; + } + + text { + text-anchor: middle; + dominant-baseline: middle; + } +} \ No newline at end of file diff --git a/src/components/flylineChartEnhanced/index.js b/src/components/flylineChartEnhanced/index.js new file mode 100644 index 0000000..c199746 --- /dev/null +++ b/src/components/flylineChartEnhanced/index.js @@ -0,0 +1,546 @@ +import React, { useEffect, useState, useRef, useCallback, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { randomExtend, getPointDistance,uuid } from '../../datav/util' + +import './style.less' + +/** +* @description Type Declaration +* +* interface Halo { +* show?: boolean +* duration?: [number, number] +* color?: string +* radius?: number +* } +* +* interface Text { +* show?: boolean +* offset?: [number, number] +* color?: string +* fontSize?: number +* } +* +* interface Icon { +* show?: boolean +* src?: string +* width?: number +* height?: number +* } +* +* interface Point { +* name: string +* coordinate: [number, number] +* halo?: Halo +* text?: Text +* icon?: Icon +* } +* +* interface Line { +* width?: number +* color?: string +* orbitColor?: string +* duration?: [number, number] +* radius?: string +* } +* +* interface Flyline extends Line { +* source: string +* target: string +* } +* +* interface FlylineWithPath extends Flyline { +* d: string +* path: [[number, number], [number, number], [number, number]] +* key: string +* } +*/ +const defaultConfig = { + /** + * @description Flyline chart points + * @type {Point[]} + * @default points = [] + */ + points: [], + /** + * @description Lines + * @type {Flyline[]} + * @default lines = [] + */ + lines: [], + /** + * @description Global halo configuration + * @type {Halo} + */ + halo: { + /** + * @description Whether to show halo + * @type {Boolean} + * @default show = false + */ + show: false, + /** + * @description Halo animation duration (1s = 10) + * @type {[number, number]} + */ + duration: [20, 30], + /** + * @description Halo color + * @type {String} + * @default color = '#fb7293' + */ + color: '#fb7293', + /** + * @description Halo radius + * @type {Number} + * @default radius = 120 + */ + radius: 120 + }, + /** + * @description Global text configuration + * @type {Text} + */ + text: { + /** + * @description Whether to show text + * @type {Boolean} + * @default show = false + */ + show: false, + /** + * @description Text offset + * @type {[number, number]} + * @default offset = [0, 15] + */ + offset: [0, 15], + /** + * @description Text color + * @type {String} + * @default color = '#ffdb5c' + */ + color: '#ffdb5c', + /** + * @description Text font size + * @type {Number} + * @default fontSize = 12 + */ + fontSize: 12 + }, + /** + * @description Global icon configuration + * @type {Icon} + */ + icon: { + /** + * @description Whether to show icon + * @type {Boolean} + * @default show = false + */ + show: false, + /** + * @description Icon src + * @type {String} + * @default src = '' + */ + src: '', + /** + * @description Icon width + * @type {Number} + * @default width = 15 + */ + width: 15, + /** + * @description Icon height + * @type {Number} + * @default width = 15 + */ + height: 15 + }, + /** + * @description Global line configuration + * @type {Line} + */ + line: { + /** + * @description Line width + * @type {Number} + * @default width = 1 + */ + width: 1, + /** + * @description Flyline color + * @type {String} + * @default color = '#ffde93' + */ + color: '#ffde93', + /** + * @description Orbit color + * @type {String} + * @default orbitColor = 'rgba(103, 224, 227, .2)' + */ + orbitColor: 'rgba(103, 224, 227, .2)', + /** + * @description Flyline animation duration + * @type {[number, number]} + * @default duration = [20, 30] + */ + duration: [20, 30], + /** + * @description Flyline radius + * @type {Number} + * @default radius = 100 + */ + radius: 100 + }, + /** + * @description Back ground image url + * @type {String} + * @default bgImgSrc = '' + */ + bgImgSrc: '', + /** + * @description K value + * @type {Number} + * @default k = -0.5 + * @example k = -1 ~ 1 + */ + k: -0.5, + /** + * @description Flyline curvature + * @type {Number} + * @default curvature = 5 + */ + curvature: 5, + /** + * @description Relative points position + * @type {Boolean} + * @default relative = true + */ + relative: true +} + +function getKLinePointByx(k, [lx, ly], x) { + const y = ly - k * lx + k * x + + return [x, y] +} + +const FlyLineChartEnhanced = forwardRef(({ config = {}, dev = false, className, style }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const { unique, flylineGradientId, haloGradientId } = useRef({ + unique: Math.random(), + flylineGradientId: `flyline-gradient-id-${uuid()}`, + haloGradientId: `halo-gradient-id-${uuid()}` + }).current + + const { mergedConfig, flylinePoints, flylines } = useMemo(calcData, [ + config, + width, + height + ]) + + const [flylineLengths, setFlylineLengths] = useState([]) + + const pathDomRef = useRef([]) + + function calcData() { + const mergedConfig = getMergedConfig() + + const flylinePoints = getFlylinePoints(mergedConfig) + + const flylines = getLinePaths(mergedConfig) + + return { mergedConfig, flylinePoints, flylines } + } + + function getMergedConfig() { + const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {}) + const { points, lines, halo, text, icon, line } = mergedConfig + + mergedConfig.points = points.map(item => { + item.halo = deepMerge(deepClone(halo, true), item.halo || {}) + item.text = deepMerge(deepClone(text, true), item.text || {}) + item.icon = deepMerge(deepClone(icon, true), item.icon || {}) + return item + }) + + mergedConfig.lines = lines.map(item => deepMerge(deepClone(line, true), item)) + + return mergedConfig + } + + function getFlylinePoints(mergedConfig) { + const { relative, points } = mergedConfig + + return points.map((item, i) => { + const { coordinate: [x, y], halo, icon, text } = item + + if (relative) item.coordinate = [x * width, y * height] + + item.halo.time = randomExtend(...halo.duration) / 10 + + const { width: iw, height: ih } = icon + item.icon.x = item.coordinate[0] - iw / 2 + item.icon.y = item.coordinate[1] - ih / 2 + + const [ox, oy] = text.offset + item.text.x = item.coordinate[0] + ox + item.text.y = item.coordinate[1] + oy + item.key = `${item.coordinate.toString()}${i}` + + return item + }) + } + + function getLinePaths(mergedConfig) { + const { points, lines } = mergedConfig + + return lines.map(item => { + const { source, target, duration } = item + const sourcePoint = points.find(({ name }) => name === source).coordinate + const targetPoint = points.find(({ name }) => name === target).coordinate + const path = getPath(sourcePoint, targetPoint, mergedConfig).map(item => item.map(v => parseFloat(v.toFixed(10)))) + const d = `M${path[0].toString()} Q${path[1].toString()} ${path[2].toString()}` + const key = `path${path.toString()}` + const time = randomExtend(...duration) / 10 + + return { ...item, path, key, d, time } + }) + } + + function getPath(start, end, mergedConfig) { + const controlPoint = getControlPoint(start, end, mergedConfig) + + return [start, controlPoint, end] + } + + function getControlPoint([sx, sy], [ex, ey], { curvature, k }) { + const [mx, my] = [(sx + ex) / 2, (sy + ey) / 2] + const distance = getPointDistance([sx, sy], [ex, ey]) + const targetLength = distance / curvature + const disDived = targetLength / 2 + let [dx, dy] = [mx, my] + + do { + dx += disDived + dy = getKLinePointByx(k, [mx, my], dx)[1] + } while (getPointDistance([mx, my], [dx, dy]) < targetLength) + + return [dx, dy] + } + + const consoleClickPos = useCallback(({ offsetX, offsetY }) => { + if (!dev) return + + const relativeX = (offsetX / width).toFixed(2) + const relativeY = (offsetY / height).toFixed(2) + + console.warn(`dv-flyline-chart-enhanced DEV: \n Click Position is [${offsetX}, ${offsetY}] \n Relative Position is [${relativeX}, ${relativeY}]`) + }, [width, height, dev]) + + useEffect(() => { + const lengths = flylines.map((foo, i) => + pathDomRef.current[i].getTotalLength() + ) + + setFlylineLengths(lengths) + }, [flylines]) + + const classNames = useMemo(() => classnames('dv-flyline-chart-enhanced', className), [ + className + ]) + + return ( +
    + { + flylines.length && ( + + + + + + + + + + + + + + {/* points */} + { + flylinePoints.map((point, i) => ( + + + { + point.halo.show && + + + + + } + + + {/* halo gradient mask */} + + { + point.halo.show && + } + + + {/* point halo */} + { + point.halo.show && + } + + {/* point icon */} + { + point.icon.show && + } + + {/* point text */} + { + point.text.show && + {point.name} + + } + + )) + } + + {/* flylines */} + { + flylines.map((line, i) => ( + + + (pathDomRef.current[i] = e)} + d={line.d} + fill='transparent' + /> + + + {/* orbit line */} + + + {/* fly line gradient mask */} + + + + + + + {/* fly line */} + { + flylineLengths[i] && + + + } + + + )) + } + + ) + } +
    + ) +}) + +FlyLineChartEnhanced.propTypes = { + config: PropTypes.object, + dev: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object +} + +// 指定 props 的默认值: +FlyLineChartEnhanced.defaultProps = { + dev: false +} + +export default FlyLineChartEnhanced diff --git a/src/components/flylineChartEnhanced/style.less b/src/components/flylineChartEnhanced/style.less new file mode 100644 index 0000000..2d32ed5 --- /dev/null +++ b/src/components/flylineChartEnhanced/style.less @@ -0,0 +1,10 @@ +.dv-flyline-chart-enhanced { + display: flex; + flex-direction: column; + background-size: 100% 100%; + + text { + text-anchor: middle; + dominant-baseline: middle; + } +} \ No newline at end of file diff --git a/src/components/fullScreenContainer/index.js b/src/components/fullScreenContainer/index.js new file mode 100644 index 0000000..24cb386 --- /dev/null +++ b/src/components/fullScreenContainer/index.js @@ -0,0 +1,44 @@ +import React, { useLayoutEffect, forwardRef } from 'react' + +import PropTypes from 'prop-types' + + +import useAutoResize from '../../datav/use/autoResize' + + +import './style.less' + +const FullScreenContainer = forwardRef(({ children, className, style }, ref) => { + const { domRef } = useAutoResize(ref) + + useLayoutEffect(() => { + const { width, height } = window.screen + + Object.assign(domRef.current.style, { + width: `${width}px`, + height: `${height}px` + }) + + domRef.current.style.transform = `scale(${document.body.clientWidth / + width})` + }) + + return ( +
    + {children} +
    + ) +}) + +FullScreenContainer.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + style: PropTypes.object +} + +export default FullScreenContainer diff --git a/src/components/fullScreenContainer/style.less b/src/components/fullScreenContainer/style.less new file mode 100644 index 0000000..24ab995 --- /dev/null +++ b/src/components/fullScreenContainer/style.less @@ -0,0 +1,8 @@ +#dv-full-screen-container { + position: fixed; + top: 0px; + left: 0px; + overflow: hidden; + transform-origin: left top; + z-index: 999; +} \ No newline at end of file diff --git a/src/components/percentPond/index.js b/src/components/percentPond/index.js new file mode 100644 index 0000000..e12cc90 --- /dev/null +++ b/src/components/percentPond/index.js @@ -0,0 +1,235 @@ +import React, { useEffect, useState, useRef, useMemo } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' + +import { uuid } from '../../datav/util' + +import './style.less' + +const defaultConfig = { + /** + * @description Value + * @type {Number} + * @default value = 0 + */ + value: 0, + /** + * @description Colors (hex|rgb|rgba|color keywords) + * @type {Array} + * @default colors = ['#00BAFF', '#3DE7C9'] + * @example colors = ['#000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)', 'red'] + */ + colors: ['#3DE7C9', '#00BAFF'], + /** + * @description Border width + * @type {Number} + * @default borderWidth = 3 + */ + borderWidth: 3, + /** + * @description Gap between border and pond + * @type {Number} + * @default borderGap = 3 + */ + borderGap: 3, + /** + * @description Line dash + * @type {Array} + * @default lineDash = [5, 1] + */ + lineDash: [5, 1], + /** + * @description Text color + * @type {String} + * @default textColor = '#fff' + */ + textColor: '#fff', + /** + * @description Border radius + * @type {Number} + * @default borderRadius = 5 + */ + borderRadius: 5, + /** + * @description Local Gradient + * @type {Boolean} + * @default localGradient = false + * @example localGradient = false | true + */ + localGradient: false, + /** + * @description Formatter + * @type {String} + * @default formatter = '{value}%' + */ + formatter: '{value}%' +} + +const PercentPond = ({ config = {}, className, style }) => { + const domRef = useRef(null) + + const { gradientId1, gradientId2 } = useRef({ + gradientId1: `percent-pond-gradientId1-${uuid()}`, + gradientId2: `percent-pond-gradientId2-${uuid()}` + }).current + + const [{ width, height, mergedConfig }, setState] = useState({ + width: 0, + height: 0, + + mergedConfig: null + }) + + const rectWidth = useMemo(() => { + if (!mergedConfig) return 0 + + const { borderWidth } = mergedConfig + + return width - borderWidth + }, [mergedConfig, width]) + + const rectHeight = useMemo(() => { + if (!mergedConfig) return 0 + + const { borderWidth } = mergedConfig + + return height - borderWidth + }, [mergedConfig, height]) + + const points = useMemo(() => { + const halfHeight = height / 2 + + if (!mergedConfig) return `0, ${halfHeight} 0, ${halfHeight}` + + const { borderWidth, borderGap, value } = mergedConfig + + const polylineLength = + ((width - (borderWidth + borderGap) * 2) / 100) * value + + return ` + ${borderWidth + borderGap}, ${halfHeight} + ${borderWidth + borderGap + polylineLength}, ${halfHeight + 0.001} + ` + }, [mergedConfig, width, height]) + + const polylineWidth = useMemo(() => { + if (!mergedConfig) return 0 + + const { borderWidth, borderGap } = mergedConfig + + return height - (borderWidth + borderGap) * 2 + }, [mergedConfig, height]) + + const linearGradient = useMemo(() => { + if (!mergedConfig) return [] + + const { colors } = mergedConfig + + const colorNum = colors.length + + const colorOffsetGap = 100 / (colorNum - 1) + + return colors.map((c, i) => [colorOffsetGap * i, c]) + }, [mergedConfig]) + + const polylineGradient = useMemo(() => { + if (!mergedConfig) return gradientId2 + + if (mergedConfig.localGradient) return gradientId1 + + return gradientId2 + }, [gradientId1, gradientId2, mergedConfig]) + + const gradient2XPos = useMemo(() => { + if (!mergedConfig) return '100%' + + const { value } = mergedConfig + + return `${200 - value}%` + }, [mergedConfig]) + + const details = useMemo(() => { + if (!mergedConfig) return '' + + const { value, formatter } = mergedConfig + + return formatter.replace('{value}', value) + }, [mergedConfig]) + + useEffect(() => { + const { clientWidth: width, clientHeight: height } = domRef.current + + setState({ + width, + height, + mergedConfig: deepMerge(deepClone(defaultConfig, true), config || {}) + }) + }, [config]) + + const classNames = useMemo(() => classnames('dv-percent-pond', className), [ + className + ]) + + return ( +
    + + + + {linearGradient.map(lc => ( + + ))} + + + + {linearGradient.map(lc => ( + + ))} + + + 0 ? rectWidth : 0} + height={rectHeight > 0 ? rectHeight : 0} + /> + + + {details} + + +
    + ) +} + +PercentPond.propTypes = { + config: PropTypes.object, + className: PropTypes.string, + style: PropTypes.object +} + +export default PercentPond diff --git a/src/components/percentPond/style.less b/src/components/percentPond/style.less new file mode 100644 index 0000000..3ab14f1 --- /dev/null +++ b/src/components/percentPond/style.less @@ -0,0 +1,24 @@ +.dv-percent-pond { + position: relative; + display: flex; + flex-direction: column; + + svg { + position: absolute; + left: 0px; + top: 0px; + width: 100%; + height: 100%; + } + + polyline { + transition: all 0.3s; + } + + text { + font-size: 25px; + font-weight: bold; + text-anchor: middle; + dominant-baseline: middle; + } +} diff --git a/src/components/scrollBoard/index.js b/src/components/scrollBoard/index.js new file mode 100644 index 0000000..a0f1f7e --- /dev/null +++ b/src/components/scrollBoard/index.js @@ -0,0 +1,417 @@ +import React, { useEffect, useState, useRef, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { co } from '../../datav/util' + + +import './style.less' + +const defaultConfig = { + /** + * @description Board header + * @type {Array} + * @default header = [] + * @example header = ['column1', 'column2', 'column3'] + */ + header: [], + /** + * @description Board data + * @type {Array} + * @default data = [] + */ + data: [], + /** + * @description Row num + * @type {Number} + * @default rowNum = 5 + */ + rowNum: 5, + /** + * @description Header background color + * @type {String} + * @default headerBGC = '#00BAFF' + */ + headerBGC: '#00BAFF', + /** + * @description Odd row background color + * @type {String} + * @default oddRowBGC = '#003B51' + */ + oddRowBGC: '#003B51', + /** + * @description Even row background color + * @type {String} + * @default evenRowBGC = '#003B51' + */ + evenRowBGC: '#0A2732', + /** + * @description Scroll wait time + * @type {Number} + * @default waitTime = 2000 + */ + waitTime: 2000, + /** + * @description Header height + * @type {Number} + * @default headerHeight = 35 + */ + headerHeight: 35, + /** + * @description Column width + * @type {Array} + * @default columnWidth = [] + */ + columnWidth: [], + /** + * @description Column align + * @type {Array} + * @default align = [] + * @example align = ['left', 'center', 'right'] + */ + align: [], + /** + * @description Show index + * @type {Boolean} + * @default index = false + */ + index: false, + /** + * @description index Header + * @type {String} + * @default indexHeader = '#' + */ + indexHeader: '#', + /** + * @description Carousel type + * @type {String} + * @default carousel = 'single' + * @example carousel = 'single' | 'page' + */ + carousel: 'single', + /** + * @description Pause scroll when mouse hovered + * @type {Boolean} + * @default hoverPause = true + * @example hoverPause = true | false + */ + hoverPause: true +} + +function calcHeaderData({ header, index, indexHeader }) { + if (!header.length) { + return [] + } + + header = [...header] + + if (index) header.unshift(indexHeader) + + return header +} + +function calcRows({ data, index, headerBGC, rowNum }) { + if (index) { + data = data.map((row, i) => { + row = [...row] + + const indexTag = `${i + + 1}` + + row.unshift(indexTag) + + return row + }) + } + + data = data.map((ceils, i) => ({ ceils, rowIndex: i })) + + const rowLength = data.length + + if (rowLength > rowNum && rowLength < 2 * rowNum) { + data = [...data, ...data] + } + + return data.map((d, i) => ({ ...d, scroll: i })) +} + +function calcAligns(mergedConfig, header) { + const columnNum = header.length + + let aligns = new Array(columnNum).fill('left') + + const { align } = mergedConfig + + return deepMerge(aligns, align) +} + +const ScrollBoard = forwardRef(({ onClick, config = {}, className, style, onMouseOver }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const [state, setState] = useState({ + mergedConfig: null, + + header: [], + + rows: [], + + widths: [], + + heights: [], + + aligns: [] + }) + + const { mergedConfig, header, rows, widths, heights, aligns } = state + + const stateRef = useRef({ + ...state, + rowsData: [], + avgHeight: 0, + animationIndex: 0 + }) + + Object.assign(stateRef.current, state) + + function onResize() { + if (!mergedConfig) return + + const widths = calcWidths(mergedConfig, stateRef.current.rowsData) + + const heights = calcHeights(mergedConfig, header) + + const data = { widths, heights } + + Object.assign(stateRef.current, data) + setState(state => ({ ...state, ...data })) + } + + function calcData() { + const mergedConfig = deepMerge( + deepClone(defaultConfig, true), + config || {} + ) + + const header = calcHeaderData(mergedConfig) + + const rows = calcRows(mergedConfig) + + const widths = calcWidths(mergedConfig, stateRef.current.rowsData) + + const heights = calcHeights(mergedConfig, header) + + const aligns = calcAligns(mergedConfig, header) + + const data = { + mergedConfig, + header, + rows, + widths, + aligns, + heights + } + + Object.assign(stateRef.current, data, { + rowsData: rows, + animationIndex: 0 + }) + + setState(state => ({ ...state, ...data })) + } + + function calcWidths({ columnWidth, header }, rowsData) { + const usedWidth = columnWidth.reduce((all, w) => all + w, 0) + + let columnNum = 0 + if (rowsData[0]) { + columnNum = rowsData[0].ceils.length + } else if (header.length) { + columnNum = header.length + } + + const avgWidth = (width - usedWidth) / (columnNum - columnWidth.length) + + const widths = new Array(columnNum).fill(avgWidth) + + return deepMerge(widths, columnWidth) + } + + function calcHeights({ headerHeight, rowNum, data }, header) { + let allHeight = height + + if (header.length) allHeight -= headerHeight + + const avgHeight = allHeight / rowNum + + Object.assign(stateRef.current, { avgHeight }) + + return new Array(data.length).fill(avgHeight) + } + + function * animation(start = false) { + let { + avgHeight, + animationIndex, + mergedConfig: { waitTime, carousel, rowNum }, + rowsData + } = stateRef.current + + const rowLength = rowsData.length + + if (start) yield new Promise(resolve => setTimeout(resolve, waitTime)) + + const animationNum = carousel === 'single' ? 1 : rowNum + + let rows = rowsData.slice(animationIndex) + rows.push(...rowsData.slice(0, animationIndex)) + rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1) + + const heights = new Array(rowLength).fill(avgHeight) + setState(state => ({ ...state, rows, heights })) + + yield new Promise(resolve => setTimeout(resolve, 300)) + + animationIndex += animationNum + + const back = animationIndex - rowLength + if (back >= 0) animationIndex = back + + const newHeights = [...heights] + newHeights.splice(0, animationNum, ...new Array(animationNum).fill(0)) + + Object.assign(stateRef.current, { animationIndex }) + setState(state => ({ ...state, heights: newHeights })) + } + + function emitEvent(handle, ri, ci, row, ceil) { + const { ceils, rowIndex } = row + + handle && handle({ row: ceils, ceil, rowIndex, columnIndex: ci }) + } + + function handleHover(enter, ri, ci, row, ceil) { + if (enter) emitEvent(onMouseOver, ri, ci, row, ceil) + + if (!mergedConfig.hoverPause) return + + const { pause, resume } = task.current + + enter && pause && resume ? pause() : (function() { if (resume) resume() })() + } + + const getBackgroundColor = rowIndex => + mergedConfig[rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC'] + + const task = useRef({}) + + useEffect(() => { + calcData() + + let start = true + + function * loop() { + while (true) { + yield * animation(start) + + start = false + + const { waitTime } = stateRef.current.mergedConfig + + yield new Promise(resolve => setTimeout(resolve, waitTime - 300)) + } + } + + const { + mergedConfig: { rowNum }, + rows: rowsData + } = stateRef.current + + const rowLength = rowsData.length + + if (rowNum >= rowLength) return + + task.current = co(loop) + + return task.current.end + }, [config, domRef.current]) + + useEffect(onResize, [width, height, domRef.current]) + + const classNames = useMemo(() => classnames('dv-scroll-board', className), [ + className + ]) + + return ( +
    + {!!header.length && !!mergedConfig && ( +
    + {header.map((headerItem, i) => ( +
    + ))} +
    + )} + + {!!mergedConfig && ( +
    + {rows.map((row, ri) => ( +
    + {row.ceils.map((ceil, ci) => ( +
    emitEvent(onClick, ri, ci, row, ceil)} + onMouseEnter={() => handleHover(true, ri, ci, row, ceil)} + onMouseLeave={() => handleHover(false)} + /> + ))} +
    + ))} +
    + )} +
    + ) +}) + +ScrollBoard.propTypes = { + config: PropTypes.object, + onClick: PropTypes.func, + onMouseOver: PropTypes.func, + className: PropTypes.string, + style: PropTypes.object +} + +export default ScrollBoard diff --git a/src/components/scrollBoard/style.less b/src/components/scrollBoard/style.less new file mode 100644 index 0000000..cb6eea5 --- /dev/null +++ b/src/components/scrollBoard/style.less @@ -0,0 +1,44 @@ +.dv-scroll-board { + position: relative; + width: 100%; + height: 100%; + color: #fff; + + .text { + padding: 0 10px; + box-sizing: border-box; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .header { + display: flex; + flex-direction: row; + font-size: 15px; + + .header-item { + .text; + transition: all 0.3s; + } + } + + .rows { + overflow: hidden; + + .row-item { + display: flex; + font-size: 14px; + transition: all 0.3s; + } + + .ceil { + .text; + } + + .index { + border-radius: 3px; + padding: 0px 3px; + } + } +} \ No newline at end of file diff --git a/src/components/scrollRankingBoard/index.js b/src/components/scrollRankingBoard/index.js new file mode 100644 index 0000000..030c156 --- /dev/null +++ b/src/components/scrollRankingBoard/index.js @@ -0,0 +1,275 @@ +import React, { useEffect, useRef, useState, useMemo, forwardRef } from 'react' + +import PropTypes from 'prop-types' + +import classnames from 'classnames' + +import { deepMerge,deepClone } from '../../datav/usefull/index' +import useAutoResize from '../../datav/use/autoResize' +import { co } from '../../datav/util' + +import './style.less' + +const defaultConfig = { + /** + * @description Board data + * @type {Array} + * @default data = [] + */ + data: [], + /** + * @description Row num + * @type {Number} + * @default rowNum = 5 + */ + rowNum: 5, + /** + * @description Scroll wait time + * @type {Number} + * @default waitTime = 2000 + */ + waitTime: 2000, + /** + * @description Carousel type + * @type {String} + * @default carousel = 'single' + * @example carousel = 'single' | 'page' + */ + carousel: 'single', + /** + * @description Value unit + * @type {String} + * @default unit = '' + * @example unit = 'ton' + */ + unit: '', + /** + * @description Auto sort by value + * @type {Boolean} + * @default sort = true + */ + sort: true, + /** + * @description Value formatter + * @type {Function} + * @default valueFormatter = null + */ + valueFormatter: null +} + +function calcRows({ data, rowNum, sort }) { + sort && data.sort(({ value: a }, { value: b }) => { + if (a > b) return -1 + if (a < b) return 1 + if (a === b) return 0 + }) + + const value = data.map(({ value }) => value) + + const min = Math.min(...value) || 0 + + // 最小值的绝对值 + const minAbs = Math.abs(min) + + const max = Math.max(...value) || 0 + + // 最大值的绝对值 + const maxAbs = Math.abs(max) + + // 总数为最大值与最小值的绝对值相加 + const total = maxAbs + minAbs + + data = data.map((row, i) => ({ + ...row, + ranking: i + 1, + percent: total && ((row.value + minAbs) / total * 100) + })) + + const rowLength = data.length + + if (rowLength > rowNum && rowLength < 2 * rowNum) { + data = [...data, ...data] + } + + data = data.map((d, i) => ({ ...d, scroll: i })) + + return data +} + +const ScrollRankingBoard = forwardRef(({ config = {}, className, style }, ref) => { + const { width, height, domRef } = useAutoResize(ref) + + const [state, setState] = useState({ + mergedConfig: null, + + rows: [], + + heights: [] + }) + + const { mergedConfig, rows, heights } = state + + const stateRef = useRef({ + ...state, + rowsData: [], + avgHeight: 0, + animationIndex: 0 + }) + + const heightRef = useRef(height) + + Object.assign(stateRef.current, state) + + function onResize(onresize = false) { + if (!mergedConfig) return + + const heights = calcHeights(mergedConfig, onresize) + + if (heights !== undefined) { + Object.assign(stateRef.current, { heights }) + setState(state => ({ ...state, heights })) + } + } + + function calcData() { + const mergedConfig = deepMerge(deepClone(defaultConfig, true), config || {}) + + const rows = calcRows(mergedConfig) + + const heights = calcHeights(mergedConfig) + + const data = { mergedConfig, rows } + + heights !== undefined && Object.assign(data, { heights }) + + Object.assign(stateRef.current, data, { rowsData: rows, animationIndex: 0 }) + + setState(state => ({ ...state, ...data })) + } + + function calcHeights({ rowNum, data }, onresize = false) { + const avgHeight = height / rowNum + + Object.assign(stateRef.current, { avgHeight }) + + if (!onresize) { + return new Array(data.length).fill(avgHeight) + } + } + + function * animation(start = false) { + let { + avgHeight, + animationIndex, + mergedConfig: { waitTime, carousel, rowNum }, + rowsData + } = stateRef.current + + const rowLength = rowsData.length + + if (start) yield new Promise(resolve => setTimeout(resolve, waitTime)) + + const animationNum = carousel === 'single' ? 1 : rowNum + + let rows = rowsData.slice(animationIndex) + rows.push(...rowsData.slice(0, animationIndex)) + rows = rows.slice(0, rowNum + 1) + + const heights = new Array(rowLength).fill(avgHeight) + setState(state => ({ ...state, rows, heights })) + + yield new Promise(resolve => setTimeout(resolve, 300)) + + animationIndex += animationNum + + const back = animationIndex - rowLength + if (back >= 0) animationIndex = back + + const newHeights = [...heights] + newHeights.splice(0, animationNum, ...new Array(animationNum).fill(0)) + + Object.assign(stateRef.current, { animationIndex }) + setState(state => ({ ...state, heights: newHeights })) + } + + useEffect(() => { + calcData() + + let start = true + + function * loop() { + while (true) { + yield * animation(start) + + start = false + + const { waitTime } = stateRef.current.mergedConfig + + yield new Promise(resolve => setTimeout(resolve, waitTime - 300)) + } + } + + const { + mergedConfig: { rowNum }, + rows: rowsData + } = stateRef.current + + const rowLength = rowsData.length + + if (rowNum >= rowLength) return + + return co(loop).end + }, [config, domRef.current]) + + useEffect(() => { + if (heightRef.current === 0 && height !== 0) { + onResize() + + heightRef.current = height + } else { + onResize(true) + } + }, [width, height, domRef.current]) + + const classNames = useMemo( + () => classnames('dv-scroll-ranking-board', className), + [className] + ) + + return ( +
    + {rows.map((item, i) => ( +
    +
    +
    No.{item.ranking}
    +
    +
    + {mergedConfig.valueFormatter ? mergedConfig.valueFormatter(item) : item.value + mergedConfig.unit} +
    +
    + +
    +
    +
    +
    +
    +
    + ))} +
    + ) +}) + +ScrollRankingBoard.propTypes = { + config: PropTypes.object, + className: PropTypes.string, + style: PropTypes.object +} + +export default ScrollRankingBoard diff --git a/src/components/scrollRankingBoard/style.less b/src/components/scrollRankingBoard/style.less new file mode 100644 index 0000000..da52236 --- /dev/null +++ b/src/components/scrollRankingBoard/style.less @@ -0,0 +1,68 @@ +@color: #1370fb; + +.dv-scroll-ranking-board { + width: 100%; + height: 100%; + color: #fff; + overflow: hidden; + + .row-item { + transition: all 0.3s; + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + } + + .ranking-info { + display: flex; + width: 100%; + font-size: 13px; + + .rank { + width: 40px; + color: @color; + } + + .info-name { + flex: 1; + } + } + + .ranking-column { + border-bottom: 2px solid fade(@color, 50); + margin-top: 5px; + + .inside-column { + position: relative; + height: 6px; + background-color: @color; + margin-bottom: 2px; + border-radius: 1px; + overflow: hidden; + } + + .shine { + position: absolute; + left: 0%; + top: 2px; + height: 2px; + width: 50px; + transform: translateX(-100%); + background: radial-gradient(rgb(40, 248, 255) 5%, transparent 80%); + animation: shine 3s ease-in-out infinite alternate; + } + } +} + +@keyframes shine { + 80% { + left: 0%; + transform: translateX(-100%); + } + + 100% { + left: 100%; + transform: translateX(0%); + } +} \ No newline at end of file diff --git a/src/datav/chartsutil/index.js b/src/datav/chartsutil/index.js new file mode 100644 index 0000000..a49a71d --- /dev/null +++ b/src/datav/chartsutil/index.js @@ -0,0 +1,130 @@ +"use strict"; +import { deepClone } from '../usefull/index' +var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.filterNonNumber = filterNonNumber; +exports.deepMerge = deepMerge; +exports.mulAdd = mulAdd; +exports.mergeSameStackData = mergeSameStackData; +exports.getTwoPointDistance = getTwoPointDistance; +exports.getLinearGradientColor = getLinearGradientColor; +exports.getPolylineLength = getPolylineLength; +exports.getPointToLineDistance = getPointToLineDistance; +exports.initNeedSeries = initNeedSeries; +exports.radianToAngle = radianToAngle; + +var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); + +var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof")); + + + +function filterNonNumber(array) { + return array.filter(function (n) { + return typeof n === 'number'; + }); +} + +function deepMerge(target, merged) { + for (var key in merged) { + if (target[key] && (0, _typeof2["default"])(target[key]) === 'object') { + deepMerge(target[key], merged[key]); + continue; + } + + if ((0, _typeof2["default"])(merged[key]) === 'object') { + target[key] = (0, deepClone)(merged[key], true); + continue; + } + + target[key] = merged[key]; + } + + return target; +} + +function mulAdd(nums) { + nums = filterNonNumber(nums); + return nums.reduce(function (all, num) { + return all + num; + }, 0); +} + +function mergeSameStackData(item, series) { + var stack = item.stack; + if (!stack) return (0, _toConsumableArray2["default"])(item.data); + var stacks = series.filter(function (_ref) { + var s = _ref.stack; + return s === stack; + }); + var index = stacks.findIndex(function (_ref2) { + var d = _ref2.data; + return d === item.data; + }); + var datas = stacks.splice(0, index + 1).map(function (_ref3) { + var data = _ref3.data; + return data; + }); + var dataLength = datas[0].length; + return new Array(dataLength).fill(0).map(function (foo, i) { + return mulAdd(datas.map(function (d) { + return d[i]; + })); + }); +} + +function getTwoPointDistance(pointOne, pointTwo) { + var minusX = Math.abs(pointOne[0] - pointTwo[0]); + var minusY = Math.abs(pointOne[1] - pointTwo[1]); + return Math.sqrt(minusX * minusX + minusY * minusY); +} + +function getLinearGradientColor(ctx, begin, end, color) { + if (!ctx || !begin || !end || !color.length) return; + var colors = color; + typeof colors === 'string' && (colors = [color, color]); + var linearGradientColor = ctx.createLinearGradient.apply(ctx, (0, _toConsumableArray2["default"])(begin).concat((0, _toConsumableArray2["default"])(end))); + var colorGap = 1 / (colors.length - 1); + colors.forEach(function (c, i) { + return linearGradientColor.addColorStop(colorGap * i, c); + }); + return linearGradientColor; +} + +function getPolylineLength(points) { + var lineSegments = new Array(points.length - 1).fill(0).map(function (foo, i) { + return [points[i], points[i + 1]]; + }); + var lengths = lineSegments.map(function (item) { + return getTwoPointDistance.apply(void 0, (0, _toConsumableArray2["default"])(item)); + }); + return mulAdd(lengths); +} + +function getPointToLineDistance(point, linePointOne, linePointTwo) { + var a = getTwoPointDistance(point, linePointOne); + var b = getTwoPointDistance(point, linePointTwo); + var c = getTwoPointDistance(linePointOne, linePointTwo); + return 0.5 * Math.sqrt((a + b + c) * (a + b - c) * (a + c - b) * (b + c - a)) / c; +} + +function initNeedSeries(series, config, type) { + series = series.filter(function (_ref4) { + var st = _ref4.type; + return st === type; + }); + series = series.map(function (item) { + return deepMerge((0, deepClone)(config, true), item); + }); + return series.filter(function (_ref5) { + var show = _ref5.show; + return show; + }); +} + +function radianToAngle(radian) { + return radian / Math.PI * 180; +} \ No newline at end of file diff --git a/src/datav/use/autoResize.js b/src/datav/use/autoResize.js new file mode 100644 index 0000000..b924659 --- /dev/null +++ b/src/datav/use/autoResize.js @@ -0,0 +1,45 @@ +import { useState, useCallback, useEffect, useRef, useImperativeHandle } from 'react' +import { debounce, observerDomResize } from '../util/index' + +export default function useAutoResize(ref) { + const [state, setState] = useState({ width: 0, height: 0 }) + + const domRef = useRef(null) + + const setWH = useCallback(() => { + const { clientWidth, clientHeight } = domRef.current || { clientWidth: 0, clientHeight: 0 } + + setState({ width: clientWidth, height: clientHeight }) + + if (!domRef.current) { + console.warn('DataV: Failed to get dom node, component rendering may be abnormal!') + } else if (!clientWidth || !clientHeight) { + console.warn('DataV: Component width or height is 0px, rendering abnormality may occur!') + } + }, []) + + useImperativeHandle(ref, () => ({ setWH }), []) + + useEffect(() => { + const debounceSetWHFun = debounce(setWH, 100) + + debounceSetWHFun() + + const domObserver = observerDomResize(domRef.current, debounceSetWHFun) + + window.addEventListener('resize', debounceSetWHFun) + + return () => { + window.removeEventListener('resize', debounceSetWHFun) + + if (!domObserver) { + return + } + + domObserver.disconnect() + domObserver.takeRecords() + } + }, []) + + return { ...state, domRef, setWH } +} diff --git a/src/datav/usefull/index.js b/src/datav/usefull/index.js new file mode 100644 index 0000000..6fb19c1 --- /dev/null +++ b/src/datav/usefull/index.js @@ -0,0 +1,173 @@ +/* +import { deepMerge } from '@jiaminghi/charts/lib/util/index' +import { deepClone } from '@jiaminghi/c-render/lib/plugin/util' +import { fade } from '@jiaminghi/color' +import { getPolylineLength, deepMerge } from '@jiaminghi/charts/lib/util' +*/ + + + +/** + * @description Get the coordinates of the specified radian on the circle + */ +function getCircleRadianPoint(x, y, radius, radian) { + return [x + Math.cos(radian) * radius, y + Math.sin(radian) * radius]; +} + + + +// eslint-disable-next-line +export function deepClone(obj, cache) { + + if (cache) { cache = new Map([]); } + if (obj === null || typeof obj !== 'object') + return obj; + + + if (cache.has(obj)) + return cache.get(obj); + + // eslint-disable-next-line + var clone = Array.isArray(obj) ? [] : {}; + cache.set(obj, clone); + // @ts-ignore + Object.keys(obj).forEach(function (key) { return (clone[key] = deepClone(obj[key], cache)); }); + return clone; +} +// eslint-disable-next-line +export function debounce(callback, delay) { + var _this = this; + if (delay === void 0) { delay = 0; } + var timer = undefined; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + if (timer) + clearTimeout(timer); + timer = window.setTimeout(function () { + // @ts-ignore + callback.call.apply(callback, __spreadArray([_this], args)); + }, delay); + }; +} + + + +export function deepMerge (target, merged) { + for (var key in merged) { + if (target[key] && typeof target[key] === 'object') { + deepMerge(target[key], merged[key]) + + continue + } + + if (typeof merged[key] === 'object') { + target[key] = deepClone(merged[key], true) + + continue + } + + target[key] = merged[key] + } + + return target +} + +function filterNonNumber(array) { + return array.filter(function (n) { + return typeof n === 'number'; + }); +} + +export function mulAdd (nums) { + nums = filterNonNumber(nums) + + return nums.reduce((all, num) => all + num, 0) +} + +export function getTwoPointDistance (pointOne, pointTwo) { + const minusX = Math.abs(pointOne[0] - pointTwo[0]) + + const minusY = Math.abs(pointOne[1] - pointTwo[1]) + + return Math.sqrt(minusX * minusX + minusY * minusY) +} + +export function getPolylineLength (points) { + const lineSegments = new Array(points.length - 1) + .fill(0) + .map((foo, i) => [points[i], points[i + 1]]) + + const lengths = lineSegments.map(item => getTwoPointDistance(...item)) + + return mulAdd(lengths) +} + + +/** + * @description Test if a hex color + * @param {string} color color string + * @return {boolean} Test result + */ + +function isHex(color) { + if (typeof color !== 'string') return false; + color = color.toLowerCase(); + return /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(color); +} + + +function getRgbValueFromHex(color) { + color = color.replace('#', ''); + if (color.length === 3) color = Array.from(color).map(function (hexNum) { + return hexNum + hexNum; + }).join(''); + var colorValues = color.split(''); + return new Array(3).fill(0).map(function (_, i) { + return parseInt("0x" + colorValues[i * 2] + colorValues[i * 2 + 1]); + }); +} +/** + * @description Get the rgb value of the rgb/rgba color + * @param {string} color Hex color + * @return {RgbValue} Rgb value of the color + */ + + +function getRgbValueFromRgb(color) { + return color.replace(/rgb\(|rgba\(|\)/g, '').split(',').slice(0, 3).map(function (n) { + return parseInt(n); + }); +} + +function getColorFromRgbValue(value) { + if (!Array.isArray(value)) throw new Error("getColorFromRgbValue: " + value + " is not an array"); + var length = value.length; + if (length !== 3 && length !== 4) throw new Error("getColorFromRgbValue: value length should be 3 or 4"); + return (length === 3 ? 'rgb(' : 'rgba(') + value.join(',') + ')'; +} + +function getRgbValue(color) { + var validColor = validator(color); + var lowerColor = validColor.toLowerCase(); + return isHex(lowerColor) ? getRgbValueFromHex(lowerColor) : getRgbValueFromRgb(lowerColor); +} + +function __spreadArrays() { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +} + +function fade(color, percent) { + if (percent === void 0) { + percent = 100; + } + + var rgbValue = getRgbValue(color); + return getColorFromRgbValue(__spreadArrays(rgbValue, [percent / 100])); +} \ No newline at end of file diff --git a/src/datav/util/index.js b/src/datav/util/index.js new file mode 100644 index 0000000..314ef0e --- /dev/null +++ b/src/datav/util/index.js @@ -0,0 +1,117 @@ +export function randomExtend(minNum, maxNum) { + if (arguments.length === 1) { + return parseInt(Math.random() * minNum + 1, 10) + } else { + return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10) + } +} + +/** + * @description 将函数转成防抖动函数 + * @param {Function} 需要转成防抖动函数的函数 + * @param {number} 延迟时间(毫秒数) + * @param {boolean} 是否执行第一次 + * @return {undefined} 无返回值 + */ +export function debounce(fn, delay = 600, runFirstFn = true) { + let timer = null + + return function(...rest) { + // 清除定时器 + clearTimeout(timer) + if (runFirstFn) { + fn.apply(this, rest) + runFirstFn = false + return + } + + // 设置定时器 + timer = setTimeout(fn.bind(this, ...rest), delay) + } +} + +export function observerDomResize(dom, callback) { + const MutationObserver = + window.MutationObserver || + window.WebKitMutationObserver || + window.MozMutationObserver + + const observer = new MutationObserver(callback) + + observer.observe(dom, { + attributes: true, + attributeFilter: ['style'], + attributeOldValue: true + }) + + return observer +} + +export function getPointDistance(pointOne, pointTwo) { + const minusX = Math.abs(pointOne[0] - pointTwo[0]) + + const minusY = Math.abs(pointOne[1] - pointTwo[1]) + + return Math.sqrt(minusX * minusX + minusY * minusY) +} + +export function co(gen) { + let destroyed = false + + // 处理 return 之后 resume 的问题 + let stop = false + + let val = null + + if (typeof gen === 'function') gen = gen() + + if (!gen || typeof gen.next !== 'function') return () => ({}) + + Promise.resolve().then(() => { + destroyed || next(gen.next()) + }) + + return { + end () { + destroyed = true + + Promise.resolve().then(() => { + gen.return() + + gen = null + }) + }, + pause () { + if (!destroyed) { stop = true } + }, + resume () { + const oldVal = val + + if (!destroyed && stop) { + stop = false + + Promise.resolve(val).then(function () { + if (!destroyed && !stop && oldVal === val) { next(gen.next()) } + }) + } + } + } + + function next(ret) { + if (ret.done) return ret.value + + val = ret.value + + return Promise.resolve(ret.value).then(() => { + (!destroyed && !stop) && next(gen.next()) + }) + } +} + +export function uuid (hasHyphen) { + return (hasHyphen ? 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' : 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx').replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0 + const v = c === 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }) +} diff --git a/src/global.less b/src/global.less new file mode 100644 index 0000000..3a71f32 --- /dev/null +++ b/src/global.less @@ -0,0 +1,485 @@ +@import '~antd/es/style/reset.css'; + +@screen-xs: 480px; +@screen-sm: 576px; +@screen-md: 768px; +@screen-lg: 992px; +@screen-xl: 1200px; +@screen-xxl: 1600px; + +#root { + height: 100%; +} + +//自定义分割线 +.listDivider { + position: relative; + top: -0.06em; + display: inline-block; + margin: 0 2%; + vertical-align: middle; + border-top: 0; + border-left: 1px solid rgba(0, 0, 0, 0.06); + box-sizing: border-box; + padding: 0; + color: rgba(0, 0, 0, 0.85); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5715; + list-style: none; + font-feature-settings: 'tnum', "tnum"; +} + +.ant-btn-primary { + background: rgba(36, 114, 214, 1); +} + +// top导航样式开始 +.button-link-top { + display:flex; + margin-left: 24px; + margin-bottom: 15px; +} + +.button-link-top-item { + padding: 5px; + margin: 0 6px; + color: rgba(36, 114, 214, 1); + font-size: 12px; + border: solid 1px rgba(36, 114, 214, 1); +} + +.button-link-top-item:hover { + background: #35ADFD; + color: #fff; + border-radius: 5px; + margin: 0 6px; + font-size: 12px; + border: solid 1px rgba(36, 114, 214, 1); +} + +.button-link-top-item-click { + padding: 5px; + margin: 0 6px; + background: rgba(36, 114, 214, 1); + color: #fff; + border-radius: 5px; + font-size: 12px; + border: solid 1px rgba(36, 114, 214, 1); +} + +.button-link-top-item-click:hover { + padding: 5px; + margin: 0 6px; + background: rgba(36, 114, 214, 1); + color: #fff; + border-radius: 5px; + font-size: 12px; + border: solid 1px rgba(36, 114, 214, 1); +} +// top导航样式结束 + +//重写滚动条样式 +::-webkit-scrollbar{ + height: 10px; + width: 10px; +} + +::-webkit-scrollbar-track{ + background: rgb(234, 234, 234); + border-radius: 3px; + -webkit-box-shadow: inset 0 0 5px rgba(37, 37, 37, 0.05); +} + +::-webkit-scrollbar-thumb { + border-radius: 6px; + border-style: dashed; + background: rgb(187, 187, 187); + -webkit-box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.05); + border-color: transparent; + border-width: 2px; + background-clip: padding-box; + &:hover { + background: rgb(187, 187, 187); + } +} + +.spinWrapper { + height: 100%; +} + +//搜索 +.searchBox, .searchbox, .searchInput, .searchinput { + margin-left: 0 !important; + flex-wrap: nowrap !important; + width: 100% !important; + + > div { + padding-left: 0 !important; + + &:last-child { + padding-right: 0 !important; + } + } + + :global { + .ant-form-item { + margin-right: 0; + } + + .ant-form-item-row, .ant-picker { + width: 100%; + } + } + + .searchBtn { + display: flex; + justify-content: flex-end; + margin-bottom: 24px; + + button+button { + margin-left: 8px; + } + + > a { + margin-left: 8px; + line-height: 32px; + } + } +} + +.searchInput, .searchinput { + > div { + &:nth-child(2) { + margin-left: 12px !important; + } + + &:last-child { + padding-right: 24px !important; + margin-left: 12px !important; + } + } + + :global { + .ant-form-item-row { + width: 100%; + } + } +} + +// 表单组件公共样式 +.createForm, .updateForm, .viewForm { + :global { + .ant-form { + margin-right: 24px; + width: 100%; + } + + .ant-picker, .ant-input-number { + width: 100%; + } + } +} + +//iconfont样式 +.iconstyle { + color: #396AF2; + opacity: 0.85; + + .iconright { + margin-right: 4px; + } +} + +/*表格开始*/ +.formtable { + width: 100%; + border: 1px solid #CCCCCC; + background-color: #fff; + color: #666; + vertical-align: middle; + border-collapse: collapse; + border-spacing: 0; + table-layout: fixed; + + >tbody { + tr { + display: table-row; + vertical-align: inherit; + border-color: inherit; + height: 55px; + text-align: center; + + &:first-child { + height: 55px; + + td { + text-align: center; + } + } + + td { + border: 1px solid #CCCCCC; + padding: 5px; + max-width: 100px; + font-size: 14px; + line-height: 20px; + + .required { + // color: #ff4d4f; + + &:before { + display: inline-block; + margin-right: 4px; + color: #ff4d4f; + font-size: 14px; + font-family: SimSun, sans-serif; + line-height: 1; + content: '*'; + } + } + } + } + } + + :global { + .ant-form-item { + margin-bottom: 0; + } + + .ant-input, + .ant-select-selector, + .ant-picker, + .ant-input-affix-wrapper, + .ant-input-number { + border: 0 !important; + } + + .ant-form-item-explain { + background-color: transparent; + position: absolute; + right: 30px; + top: 5px; + } + + .ant-picker, + .ant-input { + background: transparent !important; + position: absolute; + z-index: 1; + left: 0; + top: 1px; + } + + .ant-picker-input { + input { + text-align: center; + } + } + + .ant-select-selection-item { + padding-right: 0 !important; + top: 1px; + } + } +} + +.formtableNoneBorder { + width: 100%; + border: none; + background-color: #fff; + color: #666; + vertical-align: middle; + border-collapse: collapse; + border-spacing: 0; + table-layout: fixed; + + >tbody { + tr { + display: table-row; + vertical-align: inherit; + border-color: inherit; + height: 55px; + text-align: center; + + &:first-child { + height: 55px; + + td { + text-align: center; + } + } + + td { + border: none; + padding: 15px 5px; + max-width: 100px; + font-size: 14px; + line-height: 20px; + + .required { + // color: #ff4d4f; + + &:before { + display: inline-block; + margin-right: 4px; + color: #ff4d4f; + font-size: 14px; + font-family: SimSun, sans-serif; + line-height: 1; + content: '*'; + } + } + } + + .textRight { + text-align: right; + } + } + } + + :global { + .ant-form-item { + margin-bottom: 0; + } + + .ant-form-item-explain { + background-color: transparent; + position: absolute; + right: 30px; + top: 5px; + } + + .ant-picker, + .ant-input { + background: transparent !important; + position: absolute; + z-index: 1; + left: 0; + top: 1px; + } + + .ant-picker-input { + input { + text-align: center; + } + } + + .ant-select-selection-item { + padding-right: 0 !important; + top: 1px; + } + } +} + +.errorIcon { + margin-right: 24px; + color: #f5222d; + cursor: pointer; + + span { + margin-right: 4px; + } +} + +/*表格结束*/ + +/*tree-node-title开始*/ +.tree-node-title { + display: inline-flex; + + .tree-node-action { + visibility: hidden; + + a { + margin-right: 4px; + } + + .add { + cursor: pointer; + color: #2db7f5 + } + + .delete { + cursor: pointer; + color: #f50; + } + + .view { + cursor: pointer; + } + + .edit { + cursor: pointer; + } + } +} + +.tree-node-title:hover { + .tree-node-action { + visibility: visible; + } +} +/*tree-node-title结束*/ + +//溢出文字省略 +.nowrapper { + display: inline-block; + vertical-align: middle; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +@media screen and (max-width: @screen-xl) { + .searchInput, .searchinput { + > div { + &:nth-child(2) { + margin-left: 6px !important; + } + + &:last-child { + padding-right: 12px !important; + margin-left: 6px !important; + } + } + } +} + +@media screen and (max-width: @screen-lg) { + .searchInput, .searchinput { + > div { + &:nth-child(2) { + margin-left: 2px !important; + } + + &:last-child { + padding-right: 4px !important; + margin-left: 2px !important; + } + } + } +} + +@media screen and (max-width: @screen-md) { + .searchInput, .searchinput { + > div { + &:nth-child(2) { + margin-left: 0 !important; + } + + &:last-child { + padding-right: 0 !important; + margin-left: 0 !important; + } + } + } + + .searchBox, .searchbox, .searchInput, .searchinput { + flex-wrap: wrap !important; + + > div { + width: 100% !important; + } + } +} diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js new file mode 100644 index 0000000..b874b95 --- /dev/null +++ b/src/locales/zh-CN.js @@ -0,0 +1,5 @@ +import menu from './zh-CN/menu' + +export default { + ...menu +} diff --git a/src/locales/zh-CN/menu.js b/src/locales/zh-CN/menu.js new file mode 100644 index 0000000..770da9b --- /dev/null +++ b/src/locales/zh-CN/menu.js @@ -0,0 +1,90 @@ +export default { + 'menu.login': '登录', + 'menu.home': '首页', + + // 效率管理相关菜单 + 'menu.topnavbar00.hrefficiency': '效率管理', + 'menu.topnavbar00.hrefficiency.timesheet': '工时仪表盘', + 'menu.topnavbar00.hrefficiency.staffsheet': '员工仪表盘', + 'menu.topnavbar00.hrefficiency.staffuph': '员工效率监控', + 'menu.topnavbar00.hrefficiency.deptuph': '部门效率监控', + 'menu.topnavbar00.hrefficiency.allstaffuph': '全员效率监控', + 'menu.topnavbar00.hrefficiency.workreport': '工作报表', + 'menu.topnavbar00.hrefficiency.system': '系统管理', + 'menu.topnavbar00.hrefficiency.system.organization': '组织管理', + 'menu.topnavbar00.hrefficiency.system.role': '角色配置', + 'menu.topnavbar00.hrefficiency.system.menu': '菜单配置', + + 'menu.topnavbar00': '业务导航', + 'menu.topnavbar00.sysmenu': '系统配置', + + 'menu.topnavbar00.sysmenu.system': '系统管理', + 'menu.topnavbar00.sysmenu.system.prouser': '用户', + 'menu.topnavbar00.sysmenu.system.proorg': '机构', + 'menu.topnavbar00.sysmenu.system.proauthority': '角色', + 'menu.topnavbar00.sysmenu.system.promenu': '菜单', + 'menu.topnavbar00.sysmenu.system.prodictionarys': '字典', + 'menu.topnavbar00.sysmenu.system.prooperlog': '日志', + 'menu.topnavbar00.sysmenu.system.area': '地区', + 'menu.topnavbar00.sysmenu.system.usersystem': '系统', + 'menu.topnavbar00.sysmenu.system.prodept': '部门', + + 'menu.topnavbar00.sysmenu.auth': '权限管理', + 'menu.topnavbar00.sysmenu.auth.promenuauth': '角色菜单', + 'menu.topnavbar00.sysmenu.auth.proresauth': '角色资源', + 'menu.topnavbar00.sysmenu.auth.prouserauth': '角色用户', + 'menu.topnavbar00.sysmenu.auth.protabcolumnauth': '角色字段', + 'menu.topnavbar00.sysmenu.auth.protableauth': '角色表格', + 'menu.topnavbar00.sysmenu.auth.prouserorgan': '机构用户', + 'menu.topnavbar00.sysmenu.auth.prouserorg': '数据权限', + 'menu.topnavbar00.sysmenu.auth.prouserdept': '部门用户', + 'menu.topnavbar00.sysmenu.auth.probaseresauth': '角色基础资源', + 'menu.topnavbar00.sysmenu.auth.probasemenuauth': '角色基础菜单', + + 'menu.topnavbar00.sysmenu.resource': '资源管理', + 'menu.topnavbar00.sysmenu.resource.protables': '数据表格', + 'menu.topnavbar00.sysmenu.resource.protablecolumns': '数据字段', + + 'menu.topnavbar00.sysmenu.article': '文章管理', + 'menu.topnavbar00.sysmenu.article.ebookarticle': '文章列表', + 'menu.topnavbar00.sysmenu.article.makeebookarticle': '文章新增', + 'menu.topnavbar00.sysmenu.article.modifyebookarticle': '文章编辑', + 'menu.topnavbar00.sysmenu.article.makeebookarticleview': '文章查看', + + + 'menu.topnavbar00.sysmenu.fjauth': '分级权限管理', + 'menu.topnavbar00.sysmenu.auth.fjpromenuauth': '分级角色菜单', + 'menu.topnavbar00.sysmenu.auth.fjproresauth': '分级角色资源', + 'menu.topnavbar00.sysmenu.auth.fjprouserorg': '分级数据权限', + 'menu.topnavbar00.sysmenu.auth.fjprouserauth': '分级角色用户', + 'menu.topnavbar00.sysmenu.auth.fjprobaseresauth': '分角基础资源', + 'menu.topnavbar00.sysmenu.auth.fjprobasemenuauth': '分角基础菜单', + + 'menu.topnavbar00.sysmenu.tenant': '租户管理', + 'menu.topnavbar00.sysmenu.tenant.tenantinfo': '租户', + + 'menu.topnavbar00.pocmodel': 'poc绩效', + 'menu.topnavbar00.pocmodel.manage': '用户管理及权限管理', + 'menu.topnavbar00.pocmodel.manage.userManage': '用户管理', + 'menu.topnavbar00.pocmodel.manage.roleManage': '角色管理', + 'menu.topnavbar00.pocmodel.manage.menuManage': '菜单管理', + 'menu.topnavbar00.pocmodel.manage.limitsManage': '权限管理', + + 'menu.topnavbar00.pocmodel.performanceDevelopment': '绩效奖励方案开发制作', + 'menu.topnavbar00.pocmodel.performanceDevelopment.examine': 'KPI考核项目管理', + 'menu.topnavbar00.pocmodel.performanceDevelopment.formula': '公式/函数管理', + 'menu.topnavbar00.pocmodel.performanceDevelopment.version': '版本号管理', + 'menu.topnavbar00.pocmodel.performanceDevelopment.performancePlan': '绩效方案管理', + + 'menu.topnavbar00.pocmodel.PerformanceScheduling': '绩效奖励方案调度执行', + 'menu.topnavbar00.pocmodel.PerformanceScheduling.schedulingTasks': '调度任务管理', + 'menu.topnavbar00.pocmodel.PerformanceScheduling.monthlyPerformance': '月度绩效管理', + 'menu.topnavbar00.pocmodel.PerformanceScheduling.quarterlyPerformance': '季度绩效管理', + + 'menu.topnavbar00.pocmodel.performancereview': '绩效奖励结果审核提交发布', + 'menu.topnavbar00.pocmodel.performancereview.monthlyRelease': '月度绩效发布管理', + 'menu.topnavbar00.pocmodel.performancereview.quarterlyRelease': '季度绩效发布管理', + 'menu.topnavbar00.pocmodel.performancereview.performancerelease': '绩效发布日志管理', + 'menu.topnavbar00.pocmodel.performancereview.performanceIncentives': '绩效奖励统计分析', + +} diff --git a/src/models/baseinfodata.js b/src/models/baseinfodata.js new file mode 100644 index 0000000..16e3e4b --- /dev/null +++ b/src/models/baseinfodata.js @@ -0,0 +1,327 @@ +import { + deleteByPrimaryKeyForAa01, + selectByPrimaryKeyForAa01, + insertForAa01, + updateForAa01, + deleteByMapForAa01, + updateByMapForAa01, + getOneForAa01, + getAllForAa01, + queryPageForAa01, + countForAa01, + insertBatchForAa01, + deleteBatchForAa01, + updateBatchForAa01, + uploadSingleFile +} from '@/services/api_baseinfodata'; + +export default { + namespace: 'baseinfodata', + + state: { + data: { + list: [], + pagination: {}, + }, + selectRows: [] + }, + + effects: { + *delete_by_primarykey_for_aa01({ payload }, { call, put }) { + const response = yield call(deleteByPrimaryKeyForAa01, payload); + yield put({ + type: 'deleteByPrimaryKeyForAa01', + payload: response, + }); + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + if (callback) callback(response); + }, + *select_by_primarykey_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(selectByPrimaryKeyForAa01, payload); + yield put({ + type: 'selectByPrimaryKeyForAa01', + payload: response, + }); + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *insert_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(insertForAa01, payload); + yield put({ + type: 'insertForAa01', + payload: response, + }); + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *update_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(updateForAa01, payload); + yield put({ + type: 'updateForAa01', + payload: response, + }); + + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *delete_by_map_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(deleteByMapForAa01, payload); + yield put({ + type: 'deleteByMapForAa01', + payload: response, + }); + + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *update_by_map_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(updateByMapForAa01, payload); + yield put({ + type: 'updateByMapForAa01', + payload: response, + }); + + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *get_one_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(getOneForAa01, payload); + yield put({ + type: 'getOneForAa01', + payload: response, + }); + + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *get_all_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(getAllForAa01, payload); + yield put({ + type: 'getAllForAa01', + payload: response, + }); + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *query_page_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(queryPageForAa01, payload); + yield put({ + type: 'queryPageForAa01', + payload: response, + }); + + if (callback) callback(response); + }, + *count_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(countForAa01, payload); + yield put({ + type: 'countForAa01', + payload: response, + }); + + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *insert_batch_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(insertBatchForAa01, payload); + yield put({ + type: 'insertBatchForAa01', + payload: response, + }); + + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *delete_batch_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(deleteBatchForAa01, payload); + yield put({ + type: 'deleteBatchForAa01', + payload: response, + }); + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *update_batch_for_aa01({ payload, callback }, { call, put }) { + const response = yield call(updateBatchForAa01, payload); + yield put({ + type: 'updateBatchForAa01', + payload: response, + }); + + const responsedata = yield call(queryPageForAa01, {}); + yield put({ + type: 'queryPageForAa01', + payload: responsedata, + }); + + if (callback) callback(response); + }, + *upload_single_file({ payload, callback }, { call, put }) { + const response = yield call(uploadSingleFile, payload); + yield put({ + type: 'uploadSingleFile', + payload: response, + }); + + if (callback) callback(response); + }, + *set_selectrow_for_a01tbaseinfo({ payload }, { put }) { + yield put({ + type: 'setSelectRow', + payload: payload, + }); + }, + }, + + reducers: { + setSelectRow(state, action) { + return { + ...state, + selectRows: action.payload, + }; + }, + deleteByPrimaryKeyForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + selectByPrimaryKeyForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + insertForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + updateForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + deleteByMapForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + updateByMapForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + getOneForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + getAllForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + queryPageForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + countForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + insertBatchForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + deleteBatchForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + updateBatchForAa01(state, action) { + return { + ...state, + data: action.payload, + }; + }, + }, +}; \ No newline at end of file diff --git a/src/models/globalModel.js b/src/models/globalModel.js new file mode 100644 index 0000000..5a6ddbb --- /dev/null +++ b/src/models/globalModel.js @@ -0,0 +1,161 @@ +import { + getProOrgTreeByParentidForProOrg, + getAreaTreeByParentidForArea, + updateDropNodeForProMenu, + getProDeptTreeByParentidForProDept, + getProOrgTreeByLevelCodeForProOrg, + updateDropNodeForProResourceAction, + getProMenuTreeByLevelCode, + getProMenuTreeByParentIdForProMenu, + getProEbookArticleTypeTreeByParentid +} from '@/services/api_globaldata' + +export default { + namespace: 'globaldata', + state: { + selectOrganTree: { + list:[], + }, + selectAreaTree: { + list:[], + }, + dropTree: null, + selectDeptTree: { + data: [] + }, + selectOrgTree: { + data: [] + }, + menuData: { + list: [] + } + }, + effects: { + *get_area_tree_by_parentid_for_area({ payload, callback }, { call, put }) { + const response = yield call(getAreaTreeByParentidForArea, payload) + yield put({ + type: 'getAreaTreeByParentidForArea', + payload: response, + }) + + callback && callback(response) + }, + *get_proorgtree_by_parentid_for_proorg({ payload, callback }, { call, put }) { + const response = yield call(getProOrgTreeByParentidForProOrg, payload) + yield put({ + type: 'getProOrgTreeByParentidForProOrg', + payload: response, + }) + + callback && callback(response) + }, + *update_drop_node_for_pro_menu({ payload, callback }, { call, put }) { + const response = yield call(updateDropNodeForProMenu, payload) + yield put({ + type: 'updateDropNodeForProMenu', + payload: response + }) + + callback && callback(response) + }, + *get_prodepttree_by_parentid_for_prodept({ payload, callback }, { call, put }) { + const response = yield call(getProDeptTreeByParentidForProDept, payload) + yield put({ + type: 'getProDeptTreeByParentidForProDept', + payload: response + }) + + callback && callback(response) + }, + *get_proorg_tree_by_level_code_for_proorg({ payload, callback }, { call, put }) { + const response = yield call(getProOrgTreeByLevelCodeForProOrg, payload) + yield put({ + type: 'getProOrgTreeByLevelCodeForProOrg', + payload: response + }) + + callback && callback(response) + }, + *update_drop_node_for_pro_resource_action({ payload, callback }, { call, put }) { + const response = yield call(updateDropNodeForProResourceAction, payload) + yield put({ + type: 'updateDropNodeForProResourceAction', + payload: response + }) + + callback && callback(response) + }, + *get_promenutree_by_levelcode({ payload, callback }, { call, put }) { + const response = yield call(getProMenuTreeByLevelCode, payload) + yield put({ + type: 'getProMenuTreeByLevelCode', + payload: response + }) + + callback && callback(response) + }, + *get_promenutree_by_parentid_for_promenu({ payload, callback }, { call, put }) { + const response = yield call(getProMenuTreeByParentIdForProMenu, payload) + yield put({ + type: 'getProMenuTreeByParentIdForProMenu', + payload: response + }) + + callback && callback(response) + }, + *get_proebookarticletypetree_by_parentid({ payload, callback }, { call, put }) { + const response = yield call(getProEbookArticleTypeTreeByParentid, payload) + yield put({ + type: 'getProEbookArticleTypeTreeByParentid', + payload: response + }) + + callback && callback(response) + }, + }, + reducers: { + getAreaTreeByParentidForArea(state, action) { + return { + ...state, + selectAreaTree: action.payload, + } + }, + getProOrgTreeByParentidForProOrg(state, action) { + return { + ...state, + selectOrganTree: action.payload, + } + }, + updateDropNodeForProMenu(state, action) { + return { + ...state, + dropTree: action.payload + } + }, + getProDeptTreeByParentidForProDept(state, action) { + action.payload.list = action.payload.data + return { + ...state, + selectDeptTree: action.payload + } + }, + getProOrgTreeByLevelCodeForProOrg(state, action) { + return { + ...state, + selectOrgTree: action.payload + } + }, + updateDropNodeForProResourceAction(state, action) { + return { + ...state, + dropTree: action.payload + } + }, + getProMenuTreeByLevelCode(state, action) { + return { + ...state, + menuData: action.payload + } + }, + } +} diff --git a/src/models/uploadfile.js b/src/models/uploadfile.js new file mode 100644 index 0000000..d3aa41f --- /dev/null +++ b/src/models/uploadfile.js @@ -0,0 +1,48 @@ +import { + uploadSingleFile, + downloadFile, +} from '@/services/api_uploadfile' + +export default { + namespace: 'uploadfile', + state: { + uploadFile: { + datarecord: null + }, + fileList: null + }, + effects: { + *upload_single_file({ payload, callback }, { call, put }) { + const response = yield call(uploadSingleFile, payload) + yield put({ + type: 'uploadSingleFile', + payload: response + }) + + if (callback) callback(response) + }, + *download_file({ payload, callback }, { call, put }) { + const response = yield call(downloadFile, payload) + yield put({ + type: 'downloadFile', + payload: response + }) + + if (callback) callback(response) + }, + }, + reducers: { + uploadSingleFile(state, action) { + return { + ...state, + uploadFile: action.payload + } + }, + downloadFile(state, action) { + return { + ...state, + fileList: action.payload + } + }, + } +} diff --git a/src/pages/404.tsx b/src/pages/404.tsx new file mode 100644 index 0000000..2ac0041 --- /dev/null +++ b/src/pages/404.tsx @@ -0,0 +1,18 @@ +import { Button, Result } from 'antd'; +import React from 'react'; +import { history } from '@umijs/max'; + +const NoFoundPage: React.FC<{}> = () => ( + history.push('/')}> + Back Home + + } + /> +); + +export default NoFoundPage; \ No newline at end of file diff --git a/src/pages/BlankPage.js b/src/pages/BlankPage.js new file mode 100644 index 0000000..6b0cf22 --- /dev/null +++ b/src/pages/BlankPage.js @@ -0,0 +1,18 @@ + +import React, { PureComponent } from 'react' + + + +class BlankPage extends PureComponent { + + render() { + return ( +
    + BlankPage + +
    + ) + } +} + +export default BlankPage diff --git a/src/pages/business_basicinformation/BasicInformation-Old.js b/src/pages/business_basicinformation/BasicInformation-Old.js new file mode 100644 index 0000000..afaa19d --- /dev/null +++ b/src/pages/business_basicinformation/BasicInformation-Old.js @@ -0,0 +1,1054 @@ +import React, { Fragment, PureComponent } from 'react'; +import { + DeleteOutlined, + EditOutlined, + PlusOutlined, + SearchOutlined, + RedoOutlined, + DownOutlined, + ExclamationCircleFilled, + UpOutlined, + SafetyOutlined, + ImportOutlined, + ExportOutlined, + UserOutlined, + TeamOutlined, + SettingOutlined, + FileTextOutlined, + ProjectOutlined, + WarningOutlined, + AlertOutlined, +} from '@ant-design/icons'; +import { connect, history } from '@umijs/max'; +import { Button, Card, Divider, Dropdown, message, Modal, Popconfirm, Space, Switch, Tag, Row, Col, Tabs, Tree, Progress, Input, Select } from 'antd'; +import StandardTable from '@/components/StandardTable'; +import ReactECharts from 'echarts-for-react'; + +import { MyIcon } from "@/components/Icon" +import style from "@/global.less"; +import StaffSheetCreateForm from './form/BasicInfoCreateForm'; //新增表单 +import StaffSheetUpdateForm from './form/BasicInfoUpdateForm'; //修改表单 +import StaffSheetViewForm from './form/BasicInfoViewForm'; //查看表单 +import styles from './BasicInformation.less'; +import datadictionary from "@/utils/dataDictionary"; +import { formatDate } from "@/utils/formatUtils"; +import { formatDictText, checkButtonAuthority } from "@/utils/globalCommon"; + +const { confirm } = Modal; + +//预约类型 +const sex_type = datadictionary.sex +const user_status = datadictionary.user_status +const sys_user_post = datadictionary.sys_user_post + +const mockData = { + list: [ + { + id: '01', + projectName: '安全项目一', + location: '北京朝阳区', + area: '15,000m²', + organization: '安全生产部', + staffCount: 168, + safetyLevel: '高', + }, + { + id: '02', + projectName: '安全项目二', + location: '北京朝阳区', + area: '15,000m²', + organization: '安全生产部', + staffCount: 168, + safetyLevel: '中', + }, + { + id: '03', + projectName: '安全项目三', + location: '北京朝阳区', + area: '15,000m²', + organization: '安全生产部', + staffCount: 168, + safetyLevel: '低', + }, + { + id: '04', + projectName: '安全项目四', + location: '北京朝阳区', + area: '15,000m²', + organization: '安全生产部', + staffCount: 168, + safetyLevel: '低', + }, + { + id: '05', + projectName: '安全项目五', + location: '北京朝阳区', + area: '15,000m²', + organization: '安全生产部', + staffCount: 168, + safetyLevel: '中', + }, + ], + pagination: { + total: 48, + current: 1, + pageSize: 5, + }, +} + +// 组织架构树形数据 +const treeData = [ + { + title: '安全部', + key: '0-0', + icon: , + children: [ + { + title: '安全管理科', + key: '0-0-0', + icon: , + }, + { + title: '生产科', + key: '0-0-1', + icon: , + }, + { + title: '设备科', + key: '0-0-2', + icon: , + }, + ], + }, +]; +@connect(({ staffsheet, loading }) => ({ + staffsheet, + loading: loading.models.staffsheet, +})) + +class BasicInformation extends PureComponent { + state = { + modalVisible: false, + updateModalVisible: false, + viewModalVisible: false, + expandForm: false, + selectedRows: [], + formValues: {}, + updateFormValues: {}, + viewFormValues: {}, + toggleExpand: false, + activeTab: '1', + } + + columns = [ + { + title: '编号', + dataIndex: 'id', + key: 'id', + width: 80, + fixed: 'left', + }, + { + title: '项目名称', + dataIndex: 'projectName', + key: 'projectName', + width: 150, + fixed: 'left', + }, + { + title: '地理信息', + dataIndex: 'location', + key: 'location', + width: 120, + }, + { + title: '占地面积', + dataIndex: 'area', + key: 'area', + width: 100, + }, + { + title: '组织机构', + dataIndex: 'organization', + key: 'organization', + width: 120, + }, + { + title: '职业人数', + dataIndex: 'staffCount', + key: 'staffCount', + width: 100, + render: (text) => `${text} 人`, + }, + { + title: '安全等级', + dataIndex: 'safetyLevel', + key: 'safetyLevel', + width: 100, + render: (text) => { + let color = 'green'; + if (text === '高') color = 'red'; + else if (text === '中') color = 'orange'; + return {text}; + }, + }, + { + title: '操作', + fixed: 'right', + align: 'center', + width: 200, + render: (text, record) => { + return ( + + 详情 + 修改 + 删除 + + ) + } + }, + ] + + componentDidMount() { + // 初始化页面数据 + } + + getPieChartOption = () => { + return { + tooltip: { + trigger: 'item', + formatter: '{a}
    {b}: {c}% ({d}%)' + }, + legend: { + orient: 'vertical', + right: '25%', + top: '25%', + itemWidth: 14, + itemHeight: 14, + itemGap: 20, + textStyle: { + fontSize: 14, + color: '#333', + fontWeight: 'normal' + } + }, + series: [ + { + name: '功能区域占比', + type: 'pie', + radius: ['30%', '45%'], + center: ['30%', '45%'], + avoidLabelOverlap: false, + label: { + show: false + }, + emphasis: { + scale: true, + scaleSize: 5 + }, + labelLine: { + show: false + }, + data: [ + { + value: 45, + name: '生产区', + itemStyle: { + color: '#4A90E2' + } + }, + { + value: 20, + name: '仓储区', + itemStyle: { + color: '#7ED321' + } + }, + { + value: 20, + name: '办公区', + itemStyle: { + color: '#9B59B6' + } + }, + { + value: 15, + name: '辅助区', + itemStyle: { + color: '#F5A623' + } + } + ] + } + ] + }; + } + + handleStandardTableChange = (pagination, sorter) => { + const { dispatch } = this.props + const { formValues } = this.state + + const params = { + currentPage: pagination.current, + pageSize: pagination.pageSize, + ...formValues + } + + sorter.field && (params.sorter = `${sorter.field}_${sorter.order}`) + + // dispatch({ + // type: 'prouser/query_page_for_prouser', + // payload: params + // }) + } + + handleFormReset = () => { + const { dispatch } = this.props + this.setState({ + formValues: {} + }) + + // dispatch({ + // type: 'prouser/query_page_for_prouser', + // payload: { + // resetFlag: true + // } + // }) + } + + toggleForm = () => { + const { expandForm } = this.state + this.setState({ + expandForm: !expandForm + }) + } + + handleSelectRows = rows => { + this.setState({ + selectedRows: rows + }) + } + + handleSearch = values => { + const { dispatch } = this.props + const { expandForm } = this.state + + this.setState({ + formValues: values + }) + + // dispatch({ + // type: 'prouser/query_page_for_prouser', + // payload: { + // ...values, + // resetFlag: true, + // expandForm + // } + // }) + } + + handleModalVisible = flag => { + this.setState({ + modalVisible: !!flag + }) + } + + handleUpdateModalVisible = (flag, record) => { + this.setState({ + updateModalVisible: !!flag, + updateFormValues: record || {} + }) + } + + handleViewModalVisible = (flag, record) => { + this.setState({ + viewModalVisible: !!flag, + viewFormValues: record || {} + }) + } + + handleAdd = fields => { + const { dispatch } = this.props + + dispatch({ + type: 'prouser/insert_for_prouser', + payload: { + user_id: fields.user_id, + user_name: fields.user_name, + user_name_cn: fields.user_name_cn, + user_name_en: fields.user_name_en, + password: fields.password, + email: fields.email, + phone: fields.phone, + landline: fields.landline, + sex: fields.sex, + avatar: fields.avatar, + sign: fields.sign, + tags: fields.tags, + id_card: fields.id_card, + birthday: fields.birthday, + job_status: fields.job_status, + hiredate: fields.hiredate, + departure_time: fields.departure_time, + user_type: fields.user_type, + emp_no: fields.emp_no, + access_card_no: fields.access_card_no, + country: fields.country, + province: fields.province, + city: fields.city, + address: fields.address, + work_addr: fields.work_addr, + floor: fields.floor, + inprovince: fields.inprovince, + // dept_code: fields.dept_code, + // dept_name: fields.dept_name, + inner_dept_code: fields.inner_dept_code, + org_code: fields.org_code, + org_name: fields.org_name, + inner_org_code: fields.inner_org_code, + posts: fields.posts, + wx_openid: fields.wx_openid, + wx_mpopenid: fields.wx_mpopenid, + wx_miniopenid: fields.wx_miniopenid, + wx_unionid: fields.wx_unionid, + mobile_imei: fields.mobile_imei, + device_num: fields.device_num, + al_taobao: fields.al_taobao, + al_alipay: fields.al_alipay, + al_dingding: fields.al_dingding, + is_system_user: fields.is_system_user, + mgr_type: fields.mgr_type, + pwd_security_level: fields.pwd_security_level, + pwd_update_date: fields.pwd_update_date, + last_login_ip: fields.last_login_ip, + last_login_date: fields.last_login_date, + freeze_date: fields.freeze_date, + freeze_cause: fields.freeze_cause, + zindex: fields.zindex, + wx_msg: fields.wx_msg, + email_msg: fields.email_msg, + system_msg: fields.system_msg, + remarks: fields.remarks, + status: fields.status, + creator: fields.creator, + create_date: fields.create_date, + updater: fields.updater, + update_date: fields.update_date + }, + callback: (res) => { + if (res.success == true) { + message.success('添加成功') + this.handleModalVisible() + } + } + }) + } + + handleDeleteRecord = record => { + const { dispatch } = this.props + + dispatch({ + type: 'prouser/delete_by_primarykey_for_prouser', + payload: { + recordid: record.user_id + }, + callback: res => { + if (res.success) { + message.success('删除成功') + + this.setState({ + selectedRows: [] + }) + } + } + }) + } + + handleUpdate = fields => { + const { dispatch } = this.props + + dispatch({ + type: 'prouser/update_for_prouser', + payload: { + user_id: fields.user_id, + user_name: fields.user_name, + user_name_cn: fields.user_name_cn, + user_name_en: fields.user_name_en, + password: fields.password, + email: fields.email, + phone: fields.phone, + landline: fields.landline, + sex: fields.sex, + avatar: fields.avatar, + sign: fields.sign, + tags: fields.tags, + id_card: fields.id_card, + birthday: fields.birthday, + job_status: fields.job_status, + hiredate: fields.hiredate, + departure_time: fields.departure_time, + user_type: fields.user_type, + emp_no: fields.emp_no, + access_card_no: fields.access_card_no, + country: fields.country, + province: fields.province, + city: fields.city, + address: fields.address, + work_addr: fields.work_addr, + floor: fields.floor, + inprovince: fields.inprovince, + // dept_code: fields.dept_code, + // dept_name: fields.dept_name, + inner_dept_code: fields.inner_dept_code, + org_code: fields.org_code, + org_name: fields.org_name, + inner_org_code: fields.inner_org_code, + posts: fields.posts, + wx_openid: fields.wx_openid, + wx_mpopenid: fields.wx_mpopenid, + wx_miniopenid: fields.wx_miniopenid, + wx_unionid: fields.wx_unionid, + mobile_imei: fields.mobile_imei, + device_num: fields.device_num, + al_taobao: fields.al_taobao, + al_alipay: fields.al_alipay, + al_dingding: fields.al_dingding, + is_system_user: fields.is_system_user, + mgr_type: fields.mgr_type, + pwd_security_level: fields.pwd_security_level, + pwd_update_date: fields.pwd_update_date, + last_login_ip: fields.last_login_ip, + last_login_date: fields.last_login_date, + freeze_date: fields.freeze_date, + freeze_cause: fields.freeze_cause, + zindex: fields.zindex, + wx_msg: fields.wx_msg, + email_msg: fields.email_msg, + system_msg: fields.system_msg, + remarks: fields.remarks, + status: fields.status, + creator: fields.creator, + create_date: fields.create_date, + updater: fields.updater, + update_date: fields.update_date + }, + callback: (res) => { + if (res.success === true) { + message.success('修改成功') + this.handleUpdateModalVisible() + } + } + }) + } + + // 修改用户状态 + handleUpdateUserStatus = (fields, status, msg) => { + const { dispatch } = this.props + + confirm({ + title: `确定要 ${msg} 当前用户的吗?`, + icon: , + onOk() { + dispatch({ + type: 'prouser/update_for_prouser', + payload: { + user_id: fields.user_id, + status: status, + }, + callback: (res) => { + if (res.success === true) { + message.success('修改成功') + } + } + }) + } + }) + } + + // 修改用户密码 + handleUpdateUserPassword = fields => { + const { dispatch } = this.props + confirm({ + title: '确定要重置当前用户的密码吗?', + icon: , + onOk() { + dispatch({ + type: 'prouser/resetpwd_for_prouser', + payload: { + user_id: fields.user_id + }, + callback: (res) => { + if (res.success === true) { + message.success('重置成功') + } + } + }) + } + }) + } + + handleCollapse = () => { + const { toggleExpand } = this.state + this.setState({ + toggleExpand: !toggleExpand + }) + } + + render() { + const { + staffsheet: { + data, + selectDeptTree, + selectOrganTree + }, + loading, + dispatch + } = this.props + const { + selectedRows, + modalVisible, + updateModalVisible, + viewModalVisible, + updateFormValues, + viewFormValues, + toggleExpand, + activeTab + } = this.state + + const parentMethods = { + handleAdd: this.handleAdd, + handleModalVisible: this.handleModalVisible, + dispatch: dispatch, + loading, + selectDeptTree: selectDeptTree, + selectOrganTree: selectOrganTree + } + + const updateMethods = { + handleUpdateModalVisible: this.handleUpdateModalVisible, + handleUpdate: this.handleUpdate, + dispatch: dispatch, + loading, + selectDeptTree: selectDeptTree, + selectOrganTree: selectOrganTree + } + + const viewMethods = { + handleViewModalVisible: this.handleViewModalVisible + } + + const tabItems = [ + { + key: '1', + label: '基本信息管理', + }, + { + key: '2', + label: '资质证照管理', + }, + { + key: '3', + label: '人员基础信息', + } + ]; + + return ( +
    + {/* 顶部标签页 */} +
    + this.setState({ activeTab: key })} + items={tabItems} + className="custom-tabs" + style={{ marginBottom: 0 }} + /> +
    + + + {/* 主要内容区域 */} + + {/* 左侧:组织机构人员管理 */} +
    +
    +
    + + 组织机构与人员管理 +
    + + {/* 组织架构图 */} +
    + {/* 顶部按钮 */} +
    + +
    + + {/* 连接线 - 顶部到第一层 */} +
    + + {/* 第一层水平连接线 */} +
    + + {/* 第一层垂直连接线 */} +
    +
    +
    + + {/* 第一层部门 */} +
    +
    +
    + +
    +
    安监部
    + + {/* 安监部连接线 */} +
    +
    +
    +
    +
    + +
    +
    + +
    +
    生产部
    + + {/* 生产部连接线 */} +
    +
    +
    +
    +
    + +
    +
    + +
    +
    设备部
    + + {/* 设备部连接线 */} +
    +
    +
    +
    +
    +
    + + {/* 第二层子部门 */} +
    + {/* 安监部子部门 */} +
    +
    +
    + +
    +
    安全监督科
    +
    +
    +
    + +
    +
    风险评估科
    +
    +
    + + {/* 生产部子部门 */} +
    +
    +
    + +
    +
    生产调度科
    +
    +
    +
    + +
    +
    现场管理科
    +
    +
    + + {/* 设备部子部门 */} +
    +
    +
    + +
    +
    设备维护科
    +
    +
    +
    + +
    +
    备件管理科
    +
    +
    +
    +
    +
    + + + {/* 中间:功能区域占比 */} + +
    +
    + + 功能区面积占比 +
    + +
    + +
    +
    + + + {/* 右侧:实时风险评估 */} + +
    + {/* 顶部装饰性背景层 */} +
    + + {/* 底部装饰性背景层 */} +
    + + {/* 标题 */} +
    + + 实时风险评估 +
    + + {/* 主要内容区域 */} +
    + {/* 左侧盾牌图标 */} +
    +
    + + {/* 3D效果的底座 */} +
    +
    +
    + + {/* 右侧内容 */} +
    + {/* 风险等级提示框 */} +
    + + 当前风险等级: + 重大风险 + +
    + + {/* 提示信息 */} +
    +
    + 💡 +
    +
    +
    + 提示:安全等级分为 红、橙、黄、蓝四级,当前风险等级为红区,存在重大风险 +
    +
    +
    +
    +
    +
    + + + + {/* 底部表格区域 */} +
    +
    + +
    + + + + + + + + + + + + + + + + `共 ${total} 条`} + /> + + + {modalVisible && } + + {updateFormValues && Object.keys(updateFormValues).length ? ( + + ) : null} + + {viewFormValues && Object.keys(viewFormValues).length ? ( + + ) : null} + {importModalVisible && ( + + )} + + ) + } +} + +export default BasicInfoManagement; diff --git a/src/pages/business_basicinformation/components/PersonnelBasicInfo.js b/src/pages/business_basicinformation/components/PersonnelBasicInfo.js new file mode 100644 index 0000000..1639122 --- /dev/null +++ b/src/pages/business_basicinformation/components/PersonnelBasicInfo.js @@ -0,0 +1,780 @@ +import React, { PureComponent } from 'react'; +import { + DeleteOutlined, + EditOutlined, + PlusOutlined, + SearchOutlined, + RedoOutlined, + DownOutlined, + ExclamationCircleFilled, + ExclamationCircleOutlined, + UpOutlined, + SafetyOutlined, + ImportOutlined, + ExportOutlined, + UserOutlined, + TeamOutlined, + SettingOutlined, + FileTextOutlined, + ProjectOutlined, + WarningOutlined, + AlertOutlined, +} from '@ant-design/icons'; +import { Button, Card, Divider, Dropdown, message, Modal, Popconfirm, Space, Switch, Tag, Row, Col, Tree, Progress, Input, Select } from 'antd'; +import StandardTable from '@/components/StandardTable'; +import ReactECharts from 'echarts-for-react'; + +import { MyIcon } from "@/components/Icon" +import style from "@/global.less"; +import PersonnelCreateForm from '../form/PersonnelCreateForm'; //新增表单 +import StaffSheetUpdateForm from '../form/BasicInfoUpdateForm'; //修改表单 +import StaffSheetViewForm from '../form/BasicInfoViewForm'; //查看表单 +import styles from '../BasicInformation.less'; +import datadictionary from "@/utils/dataDictionary"; +import { formatDate } from "@/utils/formatUtils"; +import { formatDictText, checkButtonAuthority } from "@/utils/globalCommon"; +import userIcon from '@/assets/basic_information/user.png'; +import contractExpireIcon from '@/assets/basic_information/contract-expire.png'; +import contractOverdueIcon from '@/assets/basic_information/contract-overdue.png'; +import certificateExpireIcon from '@/assets/basic_information/certificate-expire.png'; +// import certificateOverdueIcon from '@/assets/basic_information/certificate-overdue.png'; +import addIcon from '@/assets/basic_information/add.png'; +const { confirm } = Modal; + +//预约类型 +const sex_type = datadictionary.sex +const user_status = datadictionary.user_status +const sys_user_post = datadictionary.sys_user_post + +const mockData = { + list: [ + { + id: '01', + name: '王一一', + idCard: '411***************7659', + company: 'xxxxxxxxxxxxxxxxxxxxxxxxx', + phone: '15278967754', + category: '长期工', + contractStatus: '有效', + contractExpiry: '2025-08-26', + }, + { + id: '02', + name: '刘冰', + idCard: '411***************7659', + company: 'xxxxxxxxxxxxxxxxxxxxxxxxx', + phone: '15278967754', + category: '外包工', + contractStatus: '异常', + contractExpiry: '2025-08-26', + }, + { + id: '03', + name: '赵志远', + idCard: '411***************7659', + company: 'xxxxxxxxxxxxxxxxxxxxxxxxx', + phone: '15278967754', + category: '临时工', + contractStatus: '有效', + contractExpiry: '2025-08-26', + }, + { + id: '04', + name: '王博国', + idCard: '411***************7659', + company: 'xxxxxxxxxxxxxxxxxxxxxxxxx', + phone: '15278967754', + category: '顾问', + contractStatus: '剩余30天', + contractExpiry: '2025-08-26', + }, + { + id: '05', + name: '赵敏敏', + idCard: '411***************7659', + company: 'xxxxxxxxxxxxxxxxxxxxxxxxx', + phone: '15278967754', + category: '外包工', + contractStatus: '剩余30天', + contractExpiry: '2025-08-26', + }, + ], + pagination: { + total: 48, + current: 1, + pageSize: 5, + }, +} + +class PersonnelBasicInfo extends PureComponent { + state = { + modalVisible: false, + updateModalVisible: false, + viewModalVisible: false, + expandForm: false, + selectedRows: [], + formValues: {}, + updateFormValues: {}, + viewFormValues: {}, + toggleExpand: false, + } + + columns = [ + { + title: '编号', + dataIndex: 'id', + key: 'id', + width: 60, + fixed: 'left', + render: (text) => ( + {text} + ), + }, + { + title: '姓名', + dataIndex: 'name', + key: 'name', + width: 80, + fixed: 'left', + render: (text) => ( + {text} + ), + }, + { + title: '身份证号', + dataIndex: 'idCard', + key: 'idCard', + width: 180, + render: (text) => ( + {text} + ), + }, + { + title: '所属公司', + dataIndex: 'company', + key: 'company', + width: 250, + render: (text) => ( + {text} + ), + }, + { + title: '联系方式', + dataIndex: 'phone', + key: 'phone', + width: 120, + render: (text) => ( + {text} + ), + }, + { + title: '人员分类', + dataIndex: 'category', + key: 'category', + width: 100, + render: (text) => ( + {text} + ), + }, + { + title: '合同状态', + dataIndex: 'contractStatus', + key: 'contractStatus', + width: 100, + render: (text) => { + let color = '#2E4CD4'; + let bgColor = '#E6E9FB'; + if (text === '异常') { + color = '#FF3E48'; + bgColor = '#FFE0E2'; + } else if (text === '剩余30天') { + color = '#FF8800'; + bgColor = '#FFF3E9'; + } + return ( + + {text} + + ); + }, + }, + { + title: '合同有效期', + dataIndex: 'contractExpiry', + key: 'contractExpiry', + width: 120, + render: (text) => ( + {text} + ), + }, + { + title: '操作', + fixed: 'right', + align: 'center', + width: 100, + render: (text, record) => ( + 上传附件 + ), + }, + ] + + getPieChartOption = () => { + return { + radar: { + indicator: [ + { name: '{a|培训完成率} {b|85%}', max: 100 }, + { name: '{a|持证率} {b|92%}', max: 100 }, + { name: '{a|注册工程师占比} {b|15%}', max: 100 } + ], + center: ['45%', '50%'], + radius: 80, + splitNumber: 4, + shape: 'polygon', + axisName: { + rich: { + a: { + color: '#333', + fontSize: 12, + fontWeight: 400, + }, + b: { + color: '#2E4CD4', + fontSize: 12, + fontWeight: 400, + } + }, + formatter: function (value) { + // value 形如 "持证率 92%" + // 这里直接返回,ECharts rich 会自动处理 + return value; + } + }, + splitLine: { + lineStyle: { + color: ['#BFCBFF'] + } + }, + splitArea: { + areaStyle: { + color: ['#fff'] + } + }, + axisLine: { + lineStyle: { + color: '#E6EAF5' + } + } + }, + series: [{ + type: 'radar', + data: [ + { + value: [85, 92, 15], + areaStyle: { + color: '#499BFF' + }, + lineStyle: { + color: '#1269FF', + width: 2 + }, + symbol: 'circle', + symbolSize: 6, + itemStyle: { + color: '#2E4CD4', + borderColor: '#fff', + borderWidth: 2 + } + } + ] + }] + } + } + + getPieDepartmentDistributionChartOption = () => { + return { + grid: { + left: 0, + right: 0, + top: 70, + bottom: 0, + containLabel: true + }, + xAxis: { + type: 'category', + data: ['技术部', '销售部', '市场部', '财务部', '人事部'], + axisLine: { lineStyle: { color: '#E6EAF5' } }, + axisTick: { show: false }, + axisLabel: { color: '#666', fontSize: 13 } + }, + yAxis: { + type: 'value', + minInterval: 100, + splitLine: { lineStyle: { color: '#E6EAF5' } }, + axisLabel: { color: '#666', fontSize: 13 } + }, + series: [{ + type: 'bar', + data: [360, 450, 370, 360, 200], + barWidth: 25, + itemStyle: { + color: typeof window !== 'undefined' && window.echarts + ? new window.echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#3691FF' }, + { offset: 1, color: '#1E68FF' } + ]) + : '#1E68FF' + } + }] + }; + } + + handleStandardTableChange = (pagination, sorter) => { + const { dispatch } = this.props + const { formValues } = this.state + + const params = { + currentPage: pagination.current, + pageSize: pagination.pageSize, + ...formValues + } + + sorter.field && (params.sorter = `${sorter.field}_${sorter.order}`) + } + + handleFormReset = () => { + const { dispatch } = this.props + this.setState({ + formValues: {} + }) + } + + toggleForm = () => { + const { expandForm } = this.state + this.setState({ + expandForm: !expandForm + }) + } + + handleSelectRows = rows => { + this.setState({ + selectedRows: rows + }) + } + + handleSearch = values => { + const { dispatch } = this.props + const { expandForm } = this.state + + this.setState({ + formValues: values + }) + } + + handleModalVisible = flag => { + this.setState({ + modalVisible: !!flag + }) + } + + handleUpdateModalVisible = (flag, record) => { + this.setState({ + updateModalVisible: !!flag, + updateFormValues: record || {} + }) + } + + handleViewModalVisible = (flag, record) => { + this.setState({ + viewModalVisible: !!flag, + viewFormValues: record || {} + }) + } + + handleAdd = fields => { + const { dispatch } = this.props + // 添加逻辑 + } + + handleDeleteRecord = record => { + const { dispatch } = this.props + // 删除逻辑 + } + + handleUpdate = fields => { + const { dispatch } = this.props + // 更新逻辑 + } + + handleCollapse = () => { + const { toggleExpand } = this.state + this.setState({ + toggleExpand: !toggleExpand + }) + } + + render() { + const { + loading, + dispatch + } = this.props + const { + selectedRows, + modalVisible, + updateModalVisible, + viewModalVisible, + updateFormValues, + viewFormValues, + toggleExpand, + } = this.state + + const parentMethods = { + handleAdd: this.handleAdd, + handleModalVisible: this.handleModalVisible, + dispatch: dispatch, + loading, + } + + const updateMethods = { + handleUpdateModalVisible: this.handleUpdateModalVisible, + handleUpdate: this.handleUpdate, + dispatch: dispatch, + loading, + } + + const viewMethods = { + handleViewModalVisible: this.handleViewModalVisible + } + + return ( + + {/* 顶部统计卡片区域 */} + + +
    +
    +
    总人数
    +
    1456
    +
    +
    + 总人数 +
    +
    + + +
    +
    +
    合同即将过期
    +
    357
    +
    +
    + 合同即将过期 +
    +
    + + +
    +
    +
    合同已过期
    +
    1456
    +
    +
    + 合同已过期 +
    +
    + + +
    +
    +
    证件即将过期
    +
    1456
    +
    +
    + 证件即将过期 +
    +
    + + +
    +
    +
    证件已过期
    +
    1456
    +
    +
    + {/* 证件已过期 +
    + +
    +
    +
    +
    本月新增
    +
    1456
    +
    +
    + 本月新增 +
    +
    + + + {/* 主要内容区域 */} + + {/* 左侧:人员资质 */} + +
    +
    + + 人员资质 +
    +
    + +
    +
    + + + {/* 中间:部门人员分布 */} + +
    +
    + + 部门人员分布 +
    +
    + +
    +
    + + + {/* 右侧:到期提醒 */} + +
    +
    + + 到期提醒 +
    +
    +
    + + 沈一撒合同已到期 +
    +
    + + 赵敏敏合同临期 15 天 +
    +
    + + 赵敏敏合同临期 15 天 +
    +
    +
    + + + + {/* 底部表格区域 */} +
    +
    + +
    + + + 部门 + + + + + + + + + + + + + `共 ${total} 条`} + /> + + + {modalVisible && } + + {updateFormValues && Object.keys(updateFormValues).length ? ( + + ) : null} + + {viewFormValues && Object.keys(viewFormValues).length ? ( + + ) : null} + + ) + } +} + +export default QualificationManagement; diff --git a/src/pages/business_basicinformation/form/BasicInfoCreateForm.js b/src/pages/business_basicinformation/form/BasicInfoCreateForm.js new file mode 100644 index 0000000..bafac8c --- /dev/null +++ b/src/pages/business_basicinformation/form/BasicInfoCreateForm.js @@ -0,0 +1,167 @@ +import { useState, useEffect } from 'react' +import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd' +import SelectDeptTree from '@/components/SelectDeptTree' +import SelectOrganTree from '@/components/SelectOrganTree' +import datadictionary from '@/utils/dataDictionary' +import { formatDictOptions, verifyPhone } from '@/utils/globalCommon' +import { NumberInput } from '@/components/NumberInput' +import styles from '../BasicInformation.less' +import style from '@/global.less' +import dayjs from 'dayjs' +import { formatDate } from '@/utils/formatUtils' + +const { Item: FormItem } = Form +const { TextArea } = Input +const dictData = datadictionary + +//新增表单 +let getDeptTreeBySelectTree +let getOrganTreeBySelectTree + +const BasicInfoCreateForm = (props => { + const [form] = Form.useForm() + + const { + modalVisible, + handleAdd, + handleModalVisible, + loading, + dispatch, + selectDeptTree, + selectOrganTree + } = props + + // 清空和初始化逻辑可保留 + useEffect(() => { + form.resetFields(); + }, [modalVisible]) + + const okHandle = () => { + form.validateFields() + .then(fieldsValue => { + form.resetFields() + handleAdd(fieldsValue) + }) + .catch(() => { }) + } + + const afterClose = () => { + form.resetFields(); + } + + return ( + handleModalVisible()} + afterClose={afterClose} + confirmLoading={loading} + footer={[ + , + + ]} + > +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +}) + +export default BasicInfoCreateForm diff --git a/src/pages/business_basicinformation/form/BasicInfoImportForm.js b/src/pages/business_basicinformation/form/BasicInfoImportForm.js new file mode 100644 index 0000000..2b2ef56 --- /dev/null +++ b/src/pages/business_basicinformation/form/BasicInfoImportForm.js @@ -0,0 +1,148 @@ +import React, { useState } from 'react'; +import { Modal, Upload, message, Progress } from 'antd'; +import { UploadOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; +import styles from '../BasicInformation.less'; +import fileIcon from '@/assets/basic_information/file-icon.png'; +const BasicInfoImportForm = (props) => { + const { modalVisible, handleModalVisible, loading } = props; + const [fileList, setFileList] = useState([ + // 示例数据,实际可根据上传进度动态设置 + // { uid: '1', name: '文件名称', size: 3 * 1024 * 1024, status: 'uploading', percent: 60 }, + // ... + ]); + + const maxFiles = 10; + const maxSize = 100 * 1024 * 1024; // 100M + + const beforeUpload = (file) => { + if (file.size > maxSize) { + message.error('单个文件不能超过100M'); + return Upload.LIST_IGNORE; + } + if (fileList.length >= maxFiles) { + message.error(`最多只能上传${maxFiles}个文件`); + return Upload.LIST_IGNORE; + } + // 模拟上传进度 + file.status = 'uploading'; + file.percent = 60; + return true; + }; + + const handleChange = ({ fileList: newFileList }) => { + // 模拟每个文件60%进度 + const updatedList = newFileList.slice(0, maxFiles).map(file => ({ + ...file, + status: 'uploading', + percent: 60, + })); + setFileList(updatedList); + }; + + const handleRemove = (file) => { + setFileList(fileList.filter(item => item.uid !== file.uid)); + }; + + const handleOk = () => { + handleModalVisible(false); + setFileList([]); + }; + + const handleCancel = () => { + handleModalVisible(false); + setFileList([]); + }; + + // 文件列表渲染 + const renderFileList = () => ( +
    + {fileList.map(file => ( +
    +
    + file +
    +
    +
    {file.name || '文件名称'}
    +
    + {file.size ? `${(file.size / (1024 * 1024)).toFixed(0)} MB` : '3 MB'} +
    + +
    +
    handleRemove(file)} + > + × +
    +
    + ))} +
    + ); + + return ( + 取消, + + ]} + > +
    + {fileList.length > 0 && renderFileList()} +
    + +
    + +
    +
    + 添加文件 {fileList.length}/{maxFiles} +
    +
    +
    +
    + + 单个文件不超过 100M +
    +
    +
    + ); +}; + +export default BasicInfoImportForm; \ No newline at end of file diff --git a/src/pages/business_basicinformation/form/BasicInfoUpdateForm.js b/src/pages/business_basicinformation/form/BasicInfoUpdateForm.js new file mode 100644 index 0000000..c9b8c90 --- /dev/null +++ b/src/pages/business_basicinformation/form/BasicInfoUpdateForm.js @@ -0,0 +1,149 @@ +import { useEffect } from 'react' +import { Col, Form, Input, Modal, Row, Select } from 'antd' +import styles from '../BasicInformation.less' + +const BasicInfoUpdateForm = (props => { + const [form] = Form.useForm() + + const { + updateModalVisible, + handleUpdate, + handleUpdateModalVisible, + loading, + values = {} + } = props + + useEffect(() => { + if (updateModalVisible) { + form.setFieldsValue(values) + } + }, [updateModalVisible, values, form]) + + const okHandle = () => { + form.validateFields() + .then(fieldsValue => { + form.resetFields() + handleUpdate(fieldsValue) + }) + .catch(() => { }) + } + + const afterClose = () => { + form.resetFields(); + } + + return ( + handleUpdateModalVisible()} + afterClose={afterClose} + confirmLoading={loading} + footer={[ + , + + ]} + > +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +}) + +export default BasicInfoUpdateForm \ No newline at end of file diff --git a/src/pages/business_basicinformation/form/BasicInfoViewForm.js b/src/pages/business_basicinformation/form/BasicInfoViewForm.js new file mode 100644 index 0000000..15f6759 --- /dev/null +++ b/src/pages/business_basicinformation/form/BasicInfoViewForm.js @@ -0,0 +1,52 @@ +import React from 'react'; +import { Modal, Row, Col } from 'antd'; +import styles from '../BasicInformation.less'; + +const BasicInfoViewForm = (props) => { + const { viewModalVisible, handleViewModalVisible, values = {} } = props; + + return ( + handleViewModalVisible(false)} + > + + +
    项目名称
    +
    {values.projectName || '-'}
    + + +
    地理信息
    +
    {values.location || '-'}
    + + +
    占地面积
    +
    + {values.area ? `${values.area}` : '-'} +
    + + +
    职业人数
    +
    {values.staffCount || '-'}
    + + +
    组织机构
    +
    {values.organization || '-'}
    + + +
    安全等级
    +
    {values.safetyLevel || '-'}
    + + + + ); +}; + +export default BasicInfoViewForm; \ No newline at end of file diff --git a/src/pages/business_basicinformation/form/PersonnelCreateForm.js b/src/pages/business_basicinformation/form/PersonnelCreateForm.js new file mode 100644 index 0000000..76b3e56 --- /dev/null +++ b/src/pages/business_basicinformation/form/PersonnelCreateForm.js @@ -0,0 +1,152 @@ +import { useState, useEffect } from 'react' +import { Col, DatePicker, Form, Input, Modal, Row, Select } from 'antd' +import SelectDeptTree from '@/components/SelectDeptTree' +import SelectOrganTree from '@/components/SelectOrganTree' +import datadictionary from '@/utils/dataDictionary' +import { formatDictOptions, verifyPhone } from '@/utils/globalCommon' +import { NumberInput } from '@/components/NumberInput' +import styles from '../BasicInformation.less' +import style from '@/global.less' +import dayjs from 'dayjs' +import { formatDate } from '@/utils/formatUtils' + +const { Item: FormItem } = Form +const { TextArea } = Input +const dictData = datadictionary + +//新增表单 +let getDeptTreeBySelectTree +let getOrganTreeBySelectTree + +const PersonnelCreateForm = (props => { + const [form] = Form.useForm() + + const { + modalVisible, + handleAdd, + handleModalVisible, + loading, + dispatch, + selectDeptTree, + selectOrganTree + } = props + + // 清空和初始化逻辑可保留 + useEffect(() => { + form.resetFields(); + }, [modalVisible]) + + const okHandle = () => { + form.validateFields() + .then(fieldsValue => { + form.resetFields() + handleAdd(fieldsValue) + }) + .catch(() => { }) + } + + const afterClose = () => { + form.resetFields(); + } + + return ( + handleModalVisible()} + afterClose={afterClose} + confirmLoading={loading} + footer={[ + , + + ]} + > +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +}) + +export default PersonnelCreateForm diff --git a/src/pages/business_basicinformation/form/StaffSheetRenderAdvancedForm.js b/src/pages/business_basicinformation/form/StaffSheetRenderAdvancedForm.js new file mode 100644 index 0000000..2625938 --- /dev/null +++ b/src/pages/business_basicinformation/form/StaffSheetRenderAdvancedForm.js @@ -0,0 +1,113 @@ +import { useEffect } from 'react' +import { Button, Col, Form, Input, Row } from 'antd' +import { UpOutlined, SearchOutlined, RedoOutlined } from '@ant-design/icons' +import SelectDeptTree from '@/components/SelectDeptTree' +import SelectOrganTree from '@/components/SelectOrganTree' +import style from '@/global.less' + +const { Item: FormItem } = Form +let getDeptTreeBySelectTree +let getOrganTreeBySelectTree + +const StaffSheetRenderAdvancedForm = (props) => { + const [form] = Form.useForm() + const { dispatch, handleSearch, handleFormReset, toggleForm, selectDeptTree, selectOrganTree, params } = props + + useEffect(() => { + form.setFieldsValue({ + user_name: params?.user_name, + user_name_cn: params?.user_name_cn, + deptname: params?.deptname, + orgname: params?.orgname, + }) + }, [params]) + + const onFinish = values => { + // if (getDeptTreeBySelectTree) { + // values.dept_code = getDeptTreeBySelectTree.dept_code + // values.deptname = getDeptTreeBySelectTree.title + // } + + if (getOrganTreeBySelectTree) { + values.org_code = getOrganTreeBySelectTree.org_code + values.orgname = getOrganTreeBySelectTree.title + } + + handleSearch(values) + } + + const myHandleFormReset = () => { + form.resetFields() + handleFormReset() + } + + const selectedDeptTreeValue = (deptRecord) => { + getDeptTreeBySelectTree = deptRecord + } + + const selectedOrganTreeValue = (orgRecord) => { + getOrganTreeBySelectTree = orgRecord + } + + const parentDeptTreeMethod = { + dispatch: dispatch, + selectDeptTree: selectDeptTree, + selectedDeptTreeValue: selectedDeptTreeValue + } + + const parentOrganTreeMethod = { + dispatch: dispatch, + selectOrganTree: selectOrganTree, + selectedOrganTreeValue: selectedOrganTreeValue + } + + return ( + + + + + + + + + + + + + + + + + + + + + + + {/**/} + {/* */} + {/* */} + {/* */} + {/**/} + + +
    + + + + + toggleForm(form)}> + 收起 + +
    + + + + ) +} + +export default StaffSheetRenderAdvancedForm diff --git a/src/pages/business_basicinformation/form/StaffSheetRenderSimpleForm.js b/src/pages/business_basicinformation/form/StaffSheetRenderSimpleForm.js new file mode 100644 index 0000000..0468e76 --- /dev/null +++ b/src/pages/business_basicinformation/form/StaffSheetRenderSimpleForm.js @@ -0,0 +1,81 @@ +import { useEffect } from 'react' +import {Button, Col, Form, Input, Row, DatePicker, Select} from 'antd' +import {DownOutlined, RedoOutlined, SearchOutlined} from '@ant-design/icons' +import style from '@/global.less' +import dayjs from 'dayjs' +const { Item: FormItem } = Form + +const StaffSheetRenderSimpleForm = (props) => { + const [form] = Form.useForm() + const { handleSearch, handleFormReset, toggleForm, params } = props + + useEffect(() => { + form.setFieldsValue({ + user_name: params?.user_name, + user_name_cn: params?.user_name_cn, + }) + }, [params]) + + const onFinish = values => { + handleSearch(values) + } + + const myHandleFormReset = () => { + form.resetFields() + handleFormReset() + } + + return ( + + + + + + + + + + + + + + + + + + + + + + +
    + + + +
    + + + + ) +} + +export default StaffSheetRenderSimpleForm diff --git a/src/pages/business_hiddentrouble/HiddenTrouble.js b/src/pages/business_hiddentrouble/HiddenTrouble.js new file mode 100644 index 0000000..6ea7690 --- /dev/null +++ b/src/pages/business_hiddentrouble/HiddenTrouble.js @@ -0,0 +1,32 @@ +import MyCard from "@/pages/business_hiddentrouble/component/MyCard"; +import {Col, Row, Tabs} from "antd"; +import HiddenDangerInspection from "@/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection"; +import styles from './HiddenTrouble.less' +import HiddenDangerRighted from "@/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted"; +import HiddenDangerCount from "@/pages/business_hiddentrouble/component/hiddenDangerCount/hiddenDangerCount"; + +const items = [ + { + key: '1', + label: '隐患检查', + children: , + }, + { + key: '2', + label: '隐患整改', + children: + }, + { + key: '3', + label: '隐患统计', + children: , + }, +]; +const HiddenTrouble = () => { + return ( + <> + + + ) +} +export default HiddenTrouble diff --git a/src/pages/business_hiddentrouble/HiddenTrouble.less b/src/pages/business_hiddentrouble/HiddenTrouble.less new file mode 100644 index 0000000..56d8636 --- /dev/null +++ b/src/pages/business_hiddentrouble/HiddenTrouble.less @@ -0,0 +1,43 @@ +/* 移除下滑线 */ +.custom-tabs .ant-tabs-ink-bar { + display: none !important; +} + +/* 未选中状态 */ +.custom-tabs { + .ant-tabs-tab { + background: #f0f0f0 !important; + border: none !important; + border-radius: 6px !important; + margin-right: 8px !important; + padding: 8px 16px !important; + transition: all 0.3s ease !important; + } +} + +/* 选中状态 - 背景变为蓝色 */ +.custom-tabs { + :global(.ant-tabs-tab-active) { + background: #2E4CD4 !important; + } +} + +/* 选中状态 - 文字变为白色 */ +.custom-tabs :global(.ant-tabs-tab-active .ant-tabs-tab-btn) { + color: white !important; + font-weight: 500 !important; +} + +/* 移除底部边框 */ +.custom-tabs .ant-tabs-nav::before { + border-bottom: none !important; +} + +/* 悬停效果 */ +.custom-tabs .ant-tabs-tab:hover { + background: #e6e6e6 !important; +} + +.custom-tabs .ant-tabs-tab-active:hover { + background: #40a9ff !important; +} diff --git a/src/pages/business_hiddentrouble/component/MyCard.js b/src/pages/business_hiddentrouble/component/MyCard.js new file mode 100644 index 0000000..568de6f --- /dev/null +++ b/src/pages/business_hiddentrouble/component/MyCard.js @@ -0,0 +1,325 @@ +import Icon from "antd/es/icon"; +import {Col, Pagination, Row, Table} from "antd"; +import {Descriptions} from "antd"; +import * as echarts from 'echarts'; +import {useEffect, useRef} from "react"; + +const HourglassSvg=()=>{ + return ( + + hourglass icon + + + ) +} +const HourglassIcon=(props)=>{ + return( + + ) +} + +const MyCard = (props) => { + return ( +
    +
    +
    {props.title}
    +
    {props.num}
    + {props.flag === 1 &&
    + {props.grow === 1 ? <>⬆ : <>⬇} {props.data}% 较上周 +
    } + {props.flag===2 &&
    + {props.grow===1 ? <>⬆:<>⬇} {props.data}% 较上周 +
    } + {props.flag===3 &&
    + {props.grow===1 ? <>⬆:<>⬇} {props.data}% 较上周 +
    } +
    +
    + +
    +
    + ) +} + + +// 环形图 +const items = [ + { + key: '1', + children: '设备安全', + color:'#868BF9', + }, + { + key: '2', + children: '电气安全', + color:'#F29629', + }, + { + key: '3', + children: '消防安全', + color:'#FFD85A', + }, + { + key: '4', + children: '工艺安全', + color:'#5ED6BE', + }, + { + key: '5', + children: '储罐专业', + color:'#00AAFF', + }, + { + key: '6', + children: '其他', + color:'#5FDCF7', + }, +]; +export const PieChart=(props)=>{ + const chartRef=useRef(null) + const data=[ + { value: 5, name: '设备安全' ,itemStyle:{color:'#868BF9'}}, + { value: 5, name: '电气安全',itemStyle:{color:'#F29629'} }, + { value: 10, name: '消防安全' ,itemStyle:{color:'#FFD85A'}}, + { value: 20, name: '工艺安全',itemStyle:{color:'#5ED6BE'} }, + { value: 30, name: '储罐专业',itemStyle:{color:'#00AAFF'} }, + { value: 30, name:'其他',itemStyle:{color:'#5FDCF7'}} + ] + useEffect(() => { + const chart=echarts.init(chartRef.current) + const option = { + legend: { + show:false, + orient: 'vertical', + x: 'left', + data: ['设备安全', '电气安全', '消防安全', '工艺安全', '储罐专业','其他'] + }, + grid:{ + top: 20, + bottom: 20, + left: 20, + right: 20, + }, + series: [ + { + type: 'pie', + radius: ['50%', '70%'], + avoidLabelOverlap: false, + label: { + show: true, + formatter: '{d}%', + position: 'outsider', + }, + labelLine: { + show: true + }, + emphasis: { + label: { + show: true, + position:'center', + fontSize: '15', + fontWeight: 'bold', + formatter: function(params) { + return `${params.name}\n${params.percent}%`; + }, + + } + }, + data:data, + }, + ] + }; + chart.setOption(option) + // 响应式调整 + const handleResize = () => { + chart.resize(); + }; + + window.addEventListener('resize', handleResize); + + // 清理函数 + return () => { + window.removeEventListener('resize', handleResize); + chart.dispose(); + }; + }, []); + + return( +
    + +
    + {props.title}} + column={2} + > + {items.map((item)=>{ + return( + +
    + + {item.children} +
    +
    + ) + })} + + +
    + + + +
    + +
    + + + + ) +} + +// 柱状图 + +export const BarChart=()=>{ + const chartRef=useRef(null) + const data=[] + useEffect(() => { + const chart =echarts.init(chartRef.current) + const option = { + grid:{ + top:20, + bottom:20, + right:30, + left:30, + }, + xAxis:{ + type: 'category', + data:['每日','每周','每月','每季度','每半年','每年','季节性','专项'], + axisLine:{ + show:true, + lineStyle:{ + color:'#D5E2FF' + } + }, + axisLabel:{ + show:true, + position:'bottom', + color:'#333' + }, + + }, + yAxis:{ + type:'value', + + axisLine:{ + show:true, + min:0, + lineStyle:{ + color:'#D5E2FF' + } + + }, + axisLabel: { + show:true, + color:'#333' + }, + splitLine:{ + show:true, + lineStyle:{ + color:'#D5E2FF', + type:'dashed' + } + }, + nameTextStyle:{ + color:'#000', + fontSize:16, + fontWeight:'600', + } + }, + series:[ + { + name: '数据系列1', + type: 'bar', // 柱状图 + data: [120, 200, 150, 80, 70,123,70,90], // 数据值 + barWidth: '20%', // 柱子宽度 + itemStyle: { + color: '#007BFF', // 柱子颜色 + borderRadius: [4, 4, 0, 0] ,// 柱子圆角 + }, + label: { + show: true, + position: 'top', // 数据标签位置 + color: '#333' + }, + emphasis: { // 高亮样式 + itemStyle: { + color: '#7491db' + } + } + } + ] + + } + chart.setOption(option) + const handleResize = () => { + chart.resize(); + }; + + window.addEventListener('resize', handleResize); + + // 清理函数 + return () => { + window.removeEventListener('resize', handleResize); + chart.dispose(); + }; + }) + return( +
    +
    检查频次分布
    +
    + +
    +
    + + ) +} + +export const MyTable=(props)=>{ + const {dataSource,columns}=props + return ( + <> +
    '共 25 条', + pageSizeOptions:[5,10,20,50,100], + defaultPageSize:5, + style:{ + marginTop:50 + } + }} + style={{width:'100%'}} + /> + + + ) +} + + +export default MyCard; diff --git a/src/pages/business_hiddentrouble/component/hiddenDangerCount/hiddenDangerCount.js b/src/pages/business_hiddentrouble/component/hiddenDangerCount/hiddenDangerCount.js new file mode 100644 index 0000000..cf9e757 --- /dev/null +++ b/src/pages/business_hiddentrouble/component/hiddenDangerCount/hiddenDangerCount.js @@ -0,0 +1,100 @@ +import {Col, List, Row} from "antd"; +import MyCard from "@/pages/business_hiddentrouble/component/MyCard"; + +const headerList=[ + { + title:'总隐患数', + num:'1489', + data:'12', + flag:1, + grow:1, + backgroundColor:'#E5EEFE', + color:'#1269FF', + }, + { + title:'已整改隐患', + num:'986', + data:'12', + flag:1, + grow:1, + backgroundColor:'#D9F8E8', + color:'#1DCE74', + }, + { + title:'整改率', + num:'86%', + data:'8', + flag:2, + grow:0, + backgroundColor:'#FFF3E9', + color:'orange', + }, + { + title:'按期整改率', + num:'89%', + data:'2', + flag:3, + grow:0, + backgroundColor:'#FFE6E8', + color:'#FF3E48', + } +] +const data=[ + {name:'Q3设备故障类隐患',tip:'占比最高',num:'45%'}, + {name:'Q2安全防护隐患',tip:'显著下降',num:'32%'}, + {name:'Q4电器隐患',tip:'持续改善',num:'28%'}, + {name:'全年平均整改周期',tip:'同比缩短',num:'7天'}, +] +const TrendAnalysis=()=>{ + return( +
    + +
    1 + + {data.map((item,index)=>{ + return( +
    +
    +
    {item.name}
    +
    {item.tip}
    +
    +
    + {item.num} +
    +
    + ) + })} + + + + ) +} + +const HiddenDangerCount=()=>{ + return ( + <> +
    + + {headerList.map((item, index) => { + return
    + })} + + +
    + +
    + + + 1 + + +
    + +
    2 + 2 + + + + ) +} +export default HiddenDangerCount diff --git a/src/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection.js b/src/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection.js new file mode 100644 index 0000000..a82ed49 --- /dev/null +++ b/src/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection.js @@ -0,0 +1,198 @@ +import {Card, Col, Row, Input, Space, Select, Upload, Button, message, Tag} from "antd"; +import './hiddenDangerInspection.less' +import MyCard, {BarChart, MyTable, PieChart} from "@/pages/business_hiddentrouble/component/MyCard"; +import {ExportOutlined, UploadOutlined} from "@ant-design/icons"; +import {render} from "react-dom"; +const { Search } = Input; + +const dataList=[ + { + title:'隐患总数', + num:'169', + data:'12', + flag:1, + grow:1, + backgroundColor:'#E5EEFE', + color:'#1269FF', + }, + { + title:'已整改隐患', + num:'169', + data:'12', + flag:1, + grow:1, + backgroundColor:'#D9F8E8', + color:'#1DCE74', + }, + { + title:'整改中隐患', + num:'169', + data:'8', + flag:2, + grow:0, + backgroundColor:'#FFF3E9', + color:'orange', + }, + { + title:'超期未整改', + num:'169', + data:'2', + flag:3, + grow:0, + backgroundColor:'#FFE6E8', + color:'#FF3E48', + } +] +export const Select1=()=>{ + return( + + ) +} +export const Select2=()=>{ + return( + + ) +} +const props = { + name: 'file', + action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', + headers: { + authorization: 'authorization-text', + }, + onChange(info) { + if (info.file.status !== 'uploading') { + console.log(info.file, info.fileList); + } + if (info.file.status === 'done') { + message.success(`${info.file.name} file uploaded successfully`); + } else if (info.file.status === 'error') { + message.error(`${info.file.name} file upload failed.`); + } + }, +}; +export const Import=()=>{ + return( + + + + ) + + +} +export const Export=()=>{ + return( + + ) +} +export const levelList= { + '一般隐患':['#DAF3FF','#00AAFA'], + '中等隐患':['#FFF3E9','#FF8800'], + '极高隐患':['#FFE0E2','#FF3E48'], +} + +const columns=[ + {align:'center',title:'检查项',dataIndex:'inspectionItems',key:'inspectionItems'}, + {align:'center',title:'隐患名称',dataIndex:'name',key:'name'}, + {align:'center',title:'隐患等级',dataIndex:'level',key:'level',render:(level)=>{ + + return {level} + }}, + {align:'center',title:'登记时间',dataIndex:'createTime',key:'createTime'}, + {align:'center',title:'隐患来源',dataIndex:'source',key:'source'}, + {align:'center',title:'隐患描述',dataIndex:'description',key:'description',ellipsis:{showTitle:true}}, + {align:'center',title:'治理类型',dataIndex:'type',key:'type'}, + {align:'center',title:'治理期限',dataIndex:'deadline',key:'deadline'}, + {align:'center',title:'整改负责人',dataIndex:'head',key:'head'}, + {align:'center',title:'隐患状态',dataIndex:'state',key:'state',render:(state)=>{return {state}}}, + {align:'center',title:'验收时间',dataIndex:'acceptanceTime',key:'acceptanceTime'}, + {align:'center',title:'操作',dataIndex:'operation',key:'operation',render:(_,record)=>{ + return + }}, +] + +const dataSource=[ + {key:1,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述111111111111111111111111111111111111',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:2,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'中等隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:3,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'极高隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:4,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:5,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:6,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:7,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:8,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:9,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:10,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:11,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:12,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:13,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:14,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:15,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:16,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:17,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:18,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:19,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:20,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + +] + + +const HiddenDangerInspection=()=>{ + return( + < > +
    + + {dataList.map((item) => { + return
    + })} + + +
    + +
    + + + + + + + +
    +
    + + + + 检查频次分布 + + +
    + +
    +
    + + + + + + +
    +
    + + + +
    +
    +
    + + + + ) +} +export default HiddenDangerInspection diff --git a/src/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection.less b/src/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection.less new file mode 100644 index 0000000..b28b04f --- /dev/null +++ b/src/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection.less @@ -0,0 +1,3 @@ + + + diff --git a/src/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted.js b/src/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted.js new file mode 100644 index 0000000..aa61d7a --- /dev/null +++ b/src/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted.js @@ -0,0 +1,318 @@ +import {Button, Card, Col, Input, Row, Space, Steps, Tag} from "antd"; +import MyCard, {MyTable, PieChart} from "@/pages/business_hiddentrouble/component/MyCard"; +import {useEffect, useRef} from "react"; +import img1 from '../../../../assets/img/stepOne.png' +import img2 from '../../../../assets/img/stepTwo.png' +import img3 from '../../../../assets/img/stepThree.png' +import img4 from '../../../../assets/img/stepFour.png' + +import echarts from "echarts/lib/echarts"; +import { + Export, Import, + levelList, + Select1, Select2 +} from "@/pages/business_hiddentrouble/component/hiddenDangerInspection/hiddenDangerInspection"; +import './hiddenDangerRighted.less' +const { Search } = Input; +const dataList=[ + { + title:'待整改隐患', + num:'169', + data:'12', + flag:1, + grow:1, + backgroundColor:'#E5EEFE', + color:'#1269FF', + }, + { + title:'整改中隐患', + num:'169', + data:'12', + flag:1, + grow:1, + backgroundColor:'#D9F8E8', + color:'#1DCE74', + }, + { + title:'已完成整改', + num:'169', + data:'8', + flag:2, + grow:0, + backgroundColor:'#FFF3E9', + color:'orange', + }, + { + title:'整改完成率', + num:'89%', + data:'2', + flag:3, + grow:0, + backgroundColor:'#FFE6E8', + color:'#FF3E48', + } +] + + +const columns=[ + {align:'center',title:'检查项',dataIndex:'inspectionItems',key:'inspectionItems'}, + {align:'center',title:'隐患名称',dataIndex:'name',key:'name'}, + {align:'center',title:'隐患等级',dataIndex:'level',key:'level',render:(level)=>{ + + return {level} + }}, + {align:'center',title:'登记时间',dataIndex:'createTime',key:'createTime'}, + {align:'center',title:'隐患来源',dataIndex:'source',key:'source'}, + {align:'center',title:'隐患描述',dataIndex:'description',key:'description',ellipsis:{showTitle:true}}, + {align:'center',title:'治理类型',dataIndex:'type',key:'type'}, + {align:'center',title:'治理期限',dataIndex:'deadline',key:'deadline'}, + {align:'center',title:'整改负责人',dataIndex:'head',key:'head'}, + {align:'center',title:'隐患状态',dataIndex:'state',key:'state',render:(state)=>{return {state}}}, + {align:'center',title:'验收时间',dataIndex:'acceptanceTime',key:'acceptanceTime'}, + {align:'center',title:'操作',dataIndex:'operation',key:'operation',render:(_,record)=>{ + return
    + + +
    + }}, +] + +const dataSource=[ + {key:1,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述111111111111111111111111111111111111',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:2,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'中等隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:3,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'极高隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:4,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:5,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:6,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:7,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:8,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:9,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:10,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:11,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:12,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:13,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:14,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:15,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:16,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:17,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:18,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:19,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:20,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + {key:21,inspectionItems:'设备运行温度',name:'D10精馏车间',level:'一般隐患',createTime:'2025-03-01',source:'日常排查',description:'隐患描述',type:'限期整改',deadline:'2025-09-03',head:'张小龙',state:'已验收',acceptanceTime:'2025-09-03'}, + +] + + + +const Qiu=(props)=>{ + return( +
    + {props.num} +
    + ) +} +const items=[ + {'0': img1}, + {'1': img2}, + {'2': img3}, + {'3': img4}, + +] + +const TrendChart=()=>{ + const TrendChart=useRef(null) + useEffect(() => { + const chart=echarts.init(TrendChart.current); + const option={ + grid:{ + bottom:25, + top:20, + }, + legend:{ + show:true, + left:'50%', + top:0, + icon: 'path://M0,0 L20,0', + itemStyle:{ + color:'#00A0E9', + } + }, + xAxis:{ + type:'category', + data:['3月','4月','5月','6月','7月','8月'], + axisLabel:{ + color:'#000', + }, + axisLine:{ + lineStyle:{ + color:'#00A0E9', + type:'dashed', + } + }, + boundaryGap: false, // 关键 + axisTick: { + show: true, + alignWithLabel: true, // 关键 + inside: true, // 刻度朝外 + length: 100, // 足够长的长度(根据图表高度调整) + lineStyle: { + color: '#cccccc', + width: 1, + type: 'dashed', + } + }, + }, + yAxis:{ + type:'value', + axisLabel:{ + color:'#000', + type:'dashed', + }, + axisLine:{ + show:true, + lineStyle:{ + color:'#00A0E9', + type:'dashed', + } + }, + splitLine:{ + lineStyle:{ + color:'#00A0E9', + type:'dashed' + } + } + }, + series:[ + { + name:'整改完成率', + data:[120,200,150,80,70,110], + type:'line', + smooth:true, + lineStyle:{ + color:'#00A0E9' + }, + areaStyle:{ + color:{ + type:'linear', + x:0, + y:0, + x2:0, + y2:1, + colorStops:[ + {offset:0,color:'#00A0E9'}, + {offset:1,color:'#99D4F5 '} + ] + } + }, + symbol:'none' + } + ] + } + chart.setOption(option) + // 响应式调整 + const handleResize = () => { + chart.resize(); + }; + handleResize() + window.addEventListener('resize', handleResize); + + // 清理函数 + return () => { + window.removeEventListener('resize', handleResize); + chart.dispose(); + }; + }, []); + return( +
    +
    + 隐患整改进度趋势 +
    +
    +
    +
    + ) +} +const Workflow=()=>{ + return( +
    +
    + 隐患整改流程 +
    +
    + {items.map((item,index)=>{ + return ( +
    +
    + +
    +
    +
    +
    + ) + })} +
    +
    + ) +} + +const HiddenDangerRighted = () => { + return ( + <> +
    + + {dataList.map((item, index) => { + return
    + })} + + +
    + +
    + + + + + + + + + + +
    +
    + + + + 隐患整改列表 + + +
    + +
    +
    + + + + + + +
    +
    + + + +
    +
    +
    + + ) +} +export default HiddenDangerRighted; diff --git a/src/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted.less b/src/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted.less new file mode 100644 index 0000000..8789c1b --- /dev/null +++ b/src/pages/business_hiddentrouble/component/hiddenDangerRighted/hiddenDangerRighted.less @@ -0,0 +1,13 @@ +.qiu{ + width: 27px; + height: 27px; + border-radius: 50%; + background-color:blue; + color: #fff; + text-align: center; + line-height: 27px; +} +.box{ + display: flex; + justify-content: center; +} diff --git a/src/pages/business_system/SystemContentList.js b/src/pages/business_system/SystemContentList.js new file mode 100644 index 0000000..d7d40d3 --- /dev/null +++ b/src/pages/business_system/SystemContentList.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Outlet } from '@umijs/max'; +import './SystemContentList.less'; + +const SystemContentList = () => { + return ( +
    +
    + +
    +
    + ); +}; + +export default SystemContentList; \ No newline at end of file diff --git a/src/pages/business_system/SystemContentList.less b/src/pages/business_system/SystemContentList.less new file mode 100644 index 0000000..cf4557c --- /dev/null +++ b/src/pages/business_system/SystemContentList.less @@ -0,0 +1,10 @@ +.systemContentContainer { + width: 100%; + height: 100%; +} + +.systemContentMain { + width: 100%; + height: 100%; + padding: 12px; +} \ No newline at end of file diff --git a/src/pages/business_system/systemMenu/SystemMenuList.js b/src/pages/business_system/systemMenu/SystemMenuList.js new file mode 100644 index 0000000..ee8176c --- /dev/null +++ b/src/pages/business_system/systemMenu/SystemMenuList.js @@ -0,0 +1,408 @@ +import React from 'react'; +import { + DeleteOutlined, + EditOutlined, + PlusOutlined, + SearchOutlined, + RedoOutlined, + DownOutlined, + ExclamationCircleFilled, + UpOutlined, + InfoCircleFilled, + QuestionCircleFilled, + DownloadOutlined +} from '@ant-design/icons'; +import {connect, history} from '@umijs/max'; +import {Button, Card, Divider, Dropdown, message, Modal, Popconfirm, Space, Switch, Tag, Row, Col, Tree} from 'antd'; +import StandardTable from '@/components/StandardTable'; + +import { MyIcon } from "@/components/Icon" +import style from "@/global.less"; +import styles from './SystemMenuList.less'; +import datadictionary from "@/utils/dataDictionary"; +import {formatDate} from "@/utils/formatUtils"; +import { formatDictText, checkButtonAuthority } from "@/utils/globalCommon"; + +const { confirm } = Modal; + +// 菜单类型 +const menu_type = datadictionary.menu_type || []; +const menu_status = datadictionary.menu_status || []; +const menu_level = datadictionary.menu_level || []; + +const mockData = { + list: [ + { + id: '1', + name: '效率管理', + code: 'hrefficiency', + type: '1', + level: '1', + status: '1', + path: '/topnavbar00/hrefficiency', + component: '', + icon: 'setting', + createTime: '2023-01-01 10:00:00', + createUser: 'admin', + children: [ + { + id: '2', + name: '工时仪表盘', + code: 'timesheet', + type: '2', + level: '2', + status: '1', + path: '/topnavbar00/hrefficiency/timesheet', + component: './hrefficiency_timesheet/TimeSheetList', + icon: 'dashboard', + createTime: '2023-01-01 10:05:00', + createUser: 'admin' + }, + { + id: '3', + name: '员工仪表盘', + code: 'staffsheet', + type: '2', + level: '2', + status: '1', + path: '/topnavbar00/hrefficiency/staffsheet', + component: './hrefficiency_staffsheet/StaffSheetList', + icon: 'user', + createTime: '2023-01-01 10:10:00', + createUser: 'admin' + } + ] + }, + { + id: '4', + name: '系统管理', + code: 'system', + type: '1', + level: '1', + status: '1', + path: '/topnavbar00/hrefficiency/system', + component: './nav_system_content/SystemContentList', + icon: 'control', + createTime: '2023-01-02 14:30:00', + createUser: 'admin' + } + ], + pagination: { + total: 2, + pageSize: 10, + current: 1 + } +}; + +@connect(({ systemMenu }) => ({ + systemMenu +})) +export default class SystemMenuList extends React.Component { + state = { + selectedRows: [], + advancedFormVisible: false, + filterData: {}, + pagination: { + pageSize: 10, + current: 1 + }, + tableData: mockData.list, + treeData: [], + expandedKeys: [] + }; + + componentDidMount() { + this.getMenuList(); + this.buildTreeData(); + } + + // 获取菜单列表 + getMenuList = () => { + const { dispatch } = this.props; + const { pagination, filterData } = this.state; + + try { + dispatch({ + type: 'systemMenu/fetchList', + payload: { + pageNum: pagination.current, + pageSize: pagination.pageSize, + ...filterData + }, + callback: (res) => { + this.setState({ + tableData: res?.list || mockData.list, + pagination: { + ...pagination, + total: res?.total || mockData.pagination.total + } + }, () => { + this.buildTreeData(); + }); + } + }); + } catch (error) { + console.error('获取菜单列表失败:', error); + this.setState({ + tableData: mockData.list, + pagination: mockData.pagination + }, () => { + this.buildTreeData(); + }); + } + }; + + // 构建树数据 + buildTreeData = () => { + const { tableData } = this.state; + const treeData = this.transformToTree(tableData); + this.setState({ treeData }); + }; + + // 转换为树结构 + transformToTree = (data) => { + return data.map(item => { + const node = { + title: item.name, + key: item.id, + dataRef: item + }; + if (item.children && item.children.length > 0) { + node.children = this.transformToTree(item.children); + } + return node; + }); + }; + + // 表格选择变化 + handleSelectChange = (selectedRowKeys, selectedRows) => { + this.setState({ selectedRows }); + }; + + // 分页变化 + handleTableChange = (pagination) => { + this.setState({ + pagination: { + ...this.state.pagination, + current: pagination.current + } + }, () => { + this.getMenuList(); + }); + }; + + // 新增菜单 + handleAdd = () => { + // 新增逻辑 + message.success('新增菜单功能'); + }; + + // 编辑菜单 + handleEdit = (record) => { + // 编辑逻辑 + message.success('编辑菜单功能'); + }; + + // 删除菜单 + handleDelete = (record) => { + confirm({ + title: '确定要删除这个菜单吗?', + icon: , + onOk: () => { + // 删除逻辑 + message.success('删除菜单成功'); + } + }); + }; + + // 批量删除 + handleBatchDelete = () => { + const { selectedRows } = this.state; + if (selectedRows.length === 0) { + message.warning('请选择要删除的菜单'); + return; + } + confirm({ + title: `确定要删除选中的${selectedRows.length}个菜单吗?`, + icon: , + onOk: () => { + // 批量删除逻辑 + message.success('批量删除菜单成功'); + } + }); + }; + + // 启用/禁用菜单 + handleStatusChange = (record) => { + const newStatus = record.status === '1' ? '0' : '1'; + confirm({ + title: `确定要${newStatus === '1' ? '启用' : '禁用'}这个菜单吗?`, + icon: , + onOk: () => { + // 状态变更逻辑 + message.success(`${newStatus === '1' ? '启用' : '禁用'}菜单成功`); + } + }); + }; + + // 展开/折叠树节点 + handleExpand = (expandedKeys) => { + this.setState({ expandedKeys }); + }; + + // 点击树节点 + handleTreeSelect = (selectedKeys, info) => { + // 树节点选择逻辑 + console.log('Selected', selectedKeys, info); + }; + + render() { + const { selectedRows, tableData, pagination, treeData, expandedKeys } = this.state; + + const columns = [ + { + title: '菜单名称', + dataIndex: 'name', + key: 'name', + ellipsis: true + }, + { + title: '菜单编码', + dataIndex: 'code', + key: 'code', + ellipsis: true + }, + { + title: '菜单类型', + dataIndex: 'type', + key: 'type', + render: text => formatDictText(menu_type, text) + }, + { + title: '菜单级别', + dataIndex: 'level', + key: 'level', + render: text => formatDictText(menu_level, text) + }, + { + title: '路径', + dataIndex: 'path', + key: 'path', + ellipsis: true + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: text => ( + + {text === '1' ? '启用' : '禁用'} + + ) + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', + ellipsis: true + }, + { + title: '创建人', + dataIndex: 'createUser', + key: 'createUser', + ellipsis: true + }, + { + title: '操作', + key: 'action', + fixed: 'right', + width: 160, + render: (_, record) => ( + + + + this.handleStatusChange(record)} + /> + + ) + } + ]; + + return ( +
    + +
    +
    +

    菜单配置

    +
    +
    + + +
    +
    + + + +
    +
    +

    菜单树

    +
    + +
    +
    + + + + +
    +
    +
    + ); + } +} \ No newline at end of file diff --git a/src/pages/business_system/systemMenu/SystemMenuList.less b/src/pages/business_system/systemMenu/SystemMenuList.less new file mode 100644 index 0000000..39b5dcb --- /dev/null +++ b/src/pages/business_system/systemMenu/SystemMenuList.less @@ -0,0 +1,112 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.headerLeft { + display: flex; + align-items: center; +} + +.headerRight { + display: flex; + align-items: center; +} + +.title { + margin: 0; + font-size: 18px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.filterSection { + margin-bottom: 16px; +} + +.filterRow { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.filterItem { + margin-right: 16px; +} + +.actionButtons { + display: flex; + align-items: center; +} + +.batchAction { + margin-right: 12px; +} + +.formContainer { + padding: 24px; + background-color: #f5f5f5; +} + +.formHeader { + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid #e8e8e8; +} + +.formTitle { + margin: 0; + font-size: 16px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.formContent { + background-color: #fff; + padding: 24px; + border-radius: 2px; +} + +.formFooter { + display: flex; + justify-content: flex-end; + margin-top: 24px; +} + +.formButton { + margin-left: 12px; +} + +.menuContent { + width: 100%; +} + +.menuTreeSection { + margin-bottom: 20px; +} + +.sectionTitle { + margin: 0 0 12px 0; + font-size: 16px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.menuTree { + border: 1px solid #e8e8e8; + min-height: 200px; +} + +.tree { + background-color: transparent; +} + +.tree .ant-tree-node-selected { + background-color: #e6f7ff; +} + +.tree .ant-tree-node-selected:hover { + background-color: #bae7ff; +} \ No newline at end of file diff --git a/src/pages/business_system/systemOrganization/SystemOrganizationList.js b/src/pages/business_system/systemOrganization/SystemOrganizationList.js new file mode 100644 index 0000000..e0c919c --- /dev/null +++ b/src/pages/business_system/systemOrganization/SystemOrganizationList.js @@ -0,0 +1,314 @@ +import React from 'react'; +import { + DeleteOutlined, + EditOutlined, + PlusOutlined, + SearchOutlined, + RedoOutlined, + DownOutlined, + ExclamationCircleFilled, + UpOutlined, + InfoCircleFilled, + QuestionCircleFilled, + DownloadOutlined +} from '@ant-design/icons'; +import {connect, history} from '@umijs/max'; +import {Button, Card, Divider, Dropdown, message, Modal, Popconfirm, Space, Switch, Tag, Row, Col} from 'antd'; +import StandardTable from '@/components/StandardTable'; + +import { MyIcon } from "@/components/Icon" +import style from "@/global.less"; +import styles from './SystemOrganizationList.less'; +import datadictionary from "@/utils/dataDictionary"; +import {formatDate} from "@/utils/formatUtils"; +import { formatDictText, checkButtonAuthority } from "@/utils/globalCommon"; + +const { confirm } = Modal; + +// 组织类型 +const organization_type = datadictionary.organization_type || []; +const organization_status = datadictionary.organization_status || []; + +const mockData = { + list: [ + { + id: '1', + name: '总部', + code: 'HQ', + type: '1', + parentId: '0', + status: '1', + createTime: '2023-01-01 10:00:00', + createUser: 'admin' + }, + { + id: '2', + name: '研发部', + code: 'R&D', + type: '2', + parentId: '1', + status: '1', + createTime: '2023-01-02 14:30:00', + createUser: 'admin' + }, + { + id: '3', + name: '市场部', + code: 'MKT', + type: '2', + parentId: '1', + status: '1', + createTime: '2023-01-03 09:15:00', + createUser: 'admin' + } + ], + pagination: { + total: 3, + pageSize: 10, + current: 1 + } +}; + +@connect(({ systemOrganization }) => ({ + systemOrganization +})) +export default class SystemOrganizationList extends React.Component { + state = { + selectedRows: [], + advancedFormVisible: false, + filterData: {}, + pagination: { + pageSize: 10, + current: 1 + }, + tableData: mockData.list + }; + + componentDidMount() { + this.getOrganizationList(); + } + + // 获取组织列表 + getOrganizationList = () => { + const { dispatch } = this.props; + const { pagination, filterData } = this.state; + + try { + dispatch({ + type: 'systemOrganization/fetchList', + payload: { + pageNum: pagination.current, + pageSize: pagination.pageSize, + ...filterData + }, + callback: (res) => { + this.setState({ + tableData: res?.list || mockData.list, + pagination: { + ...pagination, + total: res?.total || mockData.pagination.total + } + }); + } + }); + } catch (error) { + console.error('获取组织列表失败:', error); + this.setState({ + tableData: mockData.list, + pagination: mockData.pagination + }); + } + }; + + // 表格选择变化 + handleSelectChange = (selectedRowKeys, selectedRows) => { + this.setState({ selectedRows }); + }; + + // 分页变化 + handleTableChange = (pagination) => { + this.setState({ + pagination: { + ...this.state.pagination, + current: pagination.current + } + }, () => { + this.getOrganizationList(); + }); + }; + + // 新增组织 + handleAdd = () => { + // 新增逻辑 + message.success('新增组织功能'); + }; + + // 编辑组织 + handleEdit = (record) => { + // 编辑逻辑 + message.success('编辑组织功能'); + }; + + // 删除组织 + handleDelete = (record) => { + confirm({ + title: '确定要删除这个组织吗?', + icon: , + onOk: () => { + // 删除逻辑 + message.success('删除组织成功'); + } + }); + }; + + // 批量删除 + handleBatchDelete = () => { + const { selectedRows } = this.state; + if (selectedRows.length === 0) { + message.warning('请选择要删除的组织'); + return; + } + confirm({ + title: `确定要删除选中的${selectedRows.length}个组织吗?`, + icon: , + onOk: () => { + // 批量删除逻辑 + message.success('批量删除组织成功'); + } + }); + }; + + // 启用/禁用组织 + handleStatusChange = (record) => { + const newStatus = record.status === '1' ? '0' : '1'; + confirm({ + title: `确定要${newStatus === '1' ? '启用' : '禁用'}这个组织吗?`, + icon: , + onOk: () => { + // 状态变更逻辑 + message.success(`${newStatus === '1' ? '启用' : '禁用'}组织成功`); + } + }); + }; + + render() { + const { selectedRows, tableData, pagination } = this.state; + + const columns = [ + { + title: '组织名称', + dataIndex: 'name', + key: 'name', + ellipsis: true + }, + { + title: '组织编码', + dataIndex: 'code', + key: 'code', + ellipsis: true + }, + { + title: '组织类型', + dataIndex: 'type', + key: 'type', + render: text => formatDictText(organization_type, text) + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: text => ( + + {text === '1' ? '启用' : '禁用'} + + ) + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', + ellipsis: true + }, + { + title: '创建人', + dataIndex: 'createUser', + key: 'createUser', + ellipsis: true + }, + { + title: '操作', + key: 'action', + fixed: 'right', + width: 160, + render: (_, record) => ( + + + + this.handleStatusChange(record)} + /> + + ) + } + ]; + + return ( +
    + +
    +
    +

    组织管理

    +
    +
    + + +
    +
    + + + + +
    +
    + ); + } +} \ No newline at end of file diff --git a/src/pages/business_system/systemOrganization/SystemOrganizationList.less b/src/pages/business_system/systemOrganization/SystemOrganizationList.less new file mode 100644 index 0000000..9b7b87b --- /dev/null +++ b/src/pages/business_system/systemOrganization/SystemOrganizationList.less @@ -0,0 +1,80 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.headerLeft { + display: flex; + align-items: center; +} + +.headerRight { + display: flex; + align-items: center; +} + +.title { + margin: 0; + font-size: 18px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.filterSection { + margin-bottom: 16px; +} + +.filterRow { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.filterItem { + margin-right: 16px; +} + +.actionButtons { + display: flex; + align-items: center; +} + +.batchAction { + margin-right: 12px; +} + +.formContainer { + padding: 24px; + background-color: #f5f5f5; +} + +.formHeader { + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid #e8e8e8; +} + +.formTitle { + margin: 0; + font-size: 16px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.formContent { + background-color: #fff; + padding: 24px; + border-radius: 2px; +} + +.formFooter { + display: flex; + justify-content: flex-end; + margin-top: 24px; +} + +.formButton { + margin-left: 12px; +} \ No newline at end of file diff --git a/src/pages/business_system/systemRole/SystemRoleList.js b/src/pages/business_system/systemRole/SystemRoleList.js new file mode 100644 index 0000000..7d805e0 --- /dev/null +++ b/src/pages/business_system/systemRole/SystemRoleList.js @@ -0,0 +1,333 @@ +import React from 'react'; +import { + DeleteOutlined, + EditOutlined, + PlusOutlined, + SearchOutlined, + RedoOutlined, + DownOutlined, + ExclamationCircleFilled, + UpOutlined, + InfoCircleFilled, + QuestionCircleFilled, + DownloadOutlined +} from '@ant-design/icons'; +import {connect, history} from '@umijs/max'; +import {Button, Card, Divider, Dropdown, message, Modal, Popconfirm, Space, Switch, Tag, Row, Col} from 'antd'; +import StandardTable from '@/components/StandardTable'; + +import { MyIcon } from "@/components/Icon" +import style from "@/global.less"; +import styles from './SystemRoleList.less'; +import datadictionary from "@/utils/dataDictionary"; +import {formatDate} from "@/utils/formatUtils"; +import { formatDictText, checkButtonAuthority } from "@/utils/globalCommon"; + +const { confirm } = Modal; + +// 角色类型 +const role_type = datadictionary.role_type || []; +const role_status = datadictionary.role_status || []; + +const mockData = { + list: [ + { + id: '1', + name: '超级管理员', + code: 'SUPER_ADMIN', + type: '1', + status: '1', + createTime: '2023-01-01 10:00:00', + createUser: 'admin', + remark: '系统最高权限角色' + }, + { + id: '2', + name: '部门管理员', + code: 'DEPT_ADMIN', + type: '2', + status: '1', + createTime: '2023-01-02 14:30:00', + createUser: 'admin', + remark: '部门级管理权限' + }, + { + id: '3', + name: '普通用户', + code: 'NORMAL_USER', + type: '3', + status: '1', + createTime: '2023-01-03 09:15:00', + createUser: 'admin', + remark: '基础功能访问权限' + } + ], + pagination: { + total: 3, + pageSize: 10, + current: 1 + } +}; + +@connect(({ systemRole }) => ({ + systemRole +})) +export default class SystemRoleList extends React.Component { + state = { + selectedRows: [], + advancedFormVisible: false, + filterData: {}, + pagination: { + pageSize: 10, + current: 1 + }, + tableData: mockData.list + }; + + componentDidMount() { + this.getRoleList(); + } + + // 获取角色列表 + getRoleList = () => { + const { dispatch } = this.props; + const { pagination, filterData } = this.state; + + try { + dispatch({ + type: 'systemRole/fetchList', + payload: { + pageNum: pagination.current, + pageSize: pagination.pageSize, + ...filterData + }, + callback: (res) => { + this.setState({ + tableData: res?.list || mockData.list, + pagination: { + ...pagination, + total: res?.total || mockData.pagination.total + } + }); + } + }); + } catch (error) { + console.error('获取角色列表失败:', error); + this.setState({ + tableData: mockData.list, + pagination: mockData.pagination + }); + } + }; + + // 表格选择变化 + handleSelectChange = (selectedRowKeys, selectedRows) => { + this.setState({ selectedRows }); + }; + + // 分页变化 + handleTableChange = (pagination) => { + this.setState({ + pagination: { + ...this.state.pagination, + current: pagination.current + } + }, () => { + this.getRoleList(); + }); + }; + + // 新增角色 + handleAdd = () => { + // 新增逻辑 + message.success('新增角色功能'); + }; + + // 编辑角色 + handleEdit = (record) => { + // 编辑逻辑 + message.success('编辑角色功能'); + }; + + // 角色授权 + handleAuth = (record) => { + // 授权逻辑 + message.success('角色授权功能'); + }; + + // 删除角色 + handleDelete = (record) => { + confirm({ + title: '确定要删除这个角色吗?', + icon: , + onOk: () => { + // 删除逻辑 + message.success('删除角色成功'); + } + }); + }; + + // 批量删除 + handleBatchDelete = () => { + const { selectedRows } = this.state; + if (selectedRows.length === 0) { + message.warning('请选择要删除的角色'); + return; + } + confirm({ + title: `确定要删除选中的${selectedRows.length}个角色吗?`, + icon: , + onOk: () => { + // 批量删除逻辑 + message.success('批量删除角色成功'); + } + }); + }; + + // 启用/禁用角色 + handleStatusChange = (record) => { + const newStatus = record.status === '1' ? '0' : '1'; + confirm({ + title: `确定要${newStatus === '1' ? '启用' : '禁用'}这个角色吗?`, + icon: , + onOk: () => { + // 状态变更逻辑 + message.success(`${newStatus === '1' ? '启用' : '禁用'}角色成功`); + } + }); + }; + + render() { + const { selectedRows, tableData, pagination } = this.state; + + const columns = [ + { + title: '角色名称', + dataIndex: 'name', + key: 'name', + ellipsis: true + }, + { + title: '角色编码', + dataIndex: 'code', + key: 'code', + ellipsis: true + }, + { + title: '角色类型', + dataIndex: 'type', + key: 'type', + render: text => formatDictText(role_type, text) + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: text => ( + + {text === '1' ? '启用' : '禁用'} + + ) + }, + { + title: '创建时间', + dataIndex: 'createTime', + key: 'createTime', + ellipsis: true + }, + { + title: '创建人', + dataIndex: 'createUser', + key: 'createUser', + ellipsis: true + }, + { + title: '备注', + dataIndex: 'remark', + key: 'remark', + ellipsis: true + }, + { + title: '操作', + key: 'action', + fixed: 'right', + width: 200, + render: (_, record) => ( + + + + + this.handleStatusChange(record)} + /> + + ) + } + ]; + + return ( +
    + +
    +
    +

    角色配置

    +
    +
    + + +
    +
    + + + + +
    +
    + ); + } +} \ No newline at end of file diff --git a/src/pages/business_system/systemRole/SystemRoleList.less b/src/pages/business_system/systemRole/SystemRoleList.less new file mode 100644 index 0000000..9b7b87b --- /dev/null +++ b/src/pages/business_system/systemRole/SystemRoleList.less @@ -0,0 +1,80 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.headerLeft { + display: flex; + align-items: center; +} + +.headerRight { + display: flex; + align-items: center; +} + +.title { + margin: 0; + font-size: 18px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.filterSection { + margin-bottom: 16px; +} + +.filterRow { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.filterItem { + margin-right: 16px; +} + +.actionButtons { + display: flex; + align-items: center; +} + +.batchAction { + margin-right: 12px; +} + +.formContainer { + padding: 24px; + background-color: #f5f5f5; +} + +.formHeader { + margin-bottom: 16px; + padding-bottom: 8px; + border-bottom: 1px solid #e8e8e8; +} + +.formTitle { + margin: 0; + font-size: 16px; + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +.formContent { + background-color: #fff; + padding: 24px; + border-radius: 2px; +} + +.formFooter { + display: flex; + justify-content: flex-end; + margin-top: 24px; +} + +.formButton { + margin-left: 12px; +} \ No newline at end of file diff --git a/src/pages/exception/1403.js b/src/pages/exception/1403.js new file mode 100644 index 0000000..070a7c2 --- /dev/null +++ b/src/pages/exception/1403.js @@ -0,0 +1,20 @@ +import { Link } from '@umijs/max' +import { Result, Button } from 'antd' +import React from 'react' +import master from '@/assets/yay.jpg' + +export default () => ( + } + style={{ + // background: , + }} + subTitle='客服在线时间: 周一到周日 24小时在线。' + // extra={ + // + // + // + // } + /> +) diff --git a/src/pages/exception/403.js b/src/pages/exception/403.js new file mode 100644 index 0000000..b787d9b --- /dev/null +++ b/src/pages/exception/403.js @@ -0,0 +1,19 @@ +import { Link } from '@umijs/max' +import { Result, Button } from 'antd' +import React from 'react' + +export default () => ( + + + + } + /> +) diff --git a/src/pages/exception/404.js b/src/pages/exception/404.js new file mode 100644 index 0000000..93de606 --- /dev/null +++ b/src/pages/exception/404.js @@ -0,0 +1,19 @@ +import { Link } from '@umijs/max' +import { Result, Button } from 'antd' +import React from 'react' + +export default () => ( + + + + } + /> +) diff --git a/src/pages/exception/500.js b/src/pages/exception/500.js new file mode 100644 index 0000000..b005e29 --- /dev/null +++ b/src/pages/exception/500.js @@ -0,0 +1,19 @@ +import { Link } from '@umijs/max' +import { Result, Button } from 'antd' +import React from 'react' + +export default () => ( + + + + } + /> +) diff --git a/src/pages/home/HomeList.js b/src/pages/home/HomeList.js new file mode 100644 index 0000000..86f2e79 --- /dev/null +++ b/src/pages/home/HomeList.js @@ -0,0 +1,24 @@ + +import React, { PureComponent } from 'react' +import { history, connect } from '@umijs/max' +import './HomeList.less' + +class HomeList extends PureComponent { + state = { + + } + + componentDidMount() { + const { dispatch } = this.props + } + + render() { + return ( +
    + HomeList +
    + ) + } +} + +export default HomeList diff --git a/src/pages/home/HomeList.less b/src/pages/home/HomeList.less new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/login/Login.js b/src/pages/login/Login.js new file mode 100644 index 0000000..ce0ccbd --- /dev/null +++ b/src/pages/login/Login.js @@ -0,0 +1,152 @@ +import { createRef } from 'react' +import GlobalComponent from '@/components/GlobalComponent' +import { connect, useModel, history } from '@umijs/max' +import './Login.less' +import login from '@/assets/login/login.png' +import { EyeInvisibleOutlined, EyeTwoTone } from '@ant-design/icons' +import { getPageQuery } from '@/utils/utils' +import { getInitialState } from '@/app' + +@connect(({ login, loading }) => ({ + login, + loading: loading.models.login +})) + +class Login extends GlobalComponent { + formRef = createRef() + state = { + isRemember: false + } + + componentDidMount() { + const { dispatch } = this.props + } + + toggleRemember = e => { + this.setState({ + isRemember: e.target.checked ?? false + }) + } + + replaceGoto = () => { + const urlParams = new URL(window.location.href) + const params = getPageQuery() + let { redirect } = params + + if (redirect) { + const redirectUrlParams = new URL(redirect) + + if (redirectUrlParams.origin === urlParams.origin) { + redirect = redirect.slice(urlParams.origin.length) + if (redirect.match(/^\/.*#/)) { + redirect = redirect.slice(redirect.indexOf('#') + 1) + } + } else { + history.replace('/') + return + } + } + + history.replace(redirect || '/') + } + + submitForm = () => { + this.formRef.current.validateFields() + .then(fieldsValue => { + const { dispatch } = this.props + const { message } = this + const params = { + username: fieldsValue?.username, + password: fieldsValue?.password + } + + dispatch({ + type: 'login/login_user', + payload: params, + callback: async res => { + if (res?.success && res?.datarecord) { + message.success('登录成功!') + await localStorage.setItem('antd-token-authority', res.datarecord.token) + + await getInitialState('refresh').then(res => { + this.replaceGoto() + }) + } + } + }) + }) + .catch(error => { }) + } + + onKeyDown = e => { + if (e.keyCode !== 13) return + + this.submitForm() + } + + contactAdmin = () => { + } + + render() { + const { Row, Col, Carousel, Input, Form, Checkbox, Button, Spin } = this + const { Item: FormItem } = Form + const { Password } = Input + const { isRemember } = this.state + const { loading } = this.props + + return ( + +
    + login + + + + + + + +
    +
    +
    统一权限管理系统
    + +
    欢迎登录
    +
    + +
    + +
    + + + + + + (visible ? : )} + /> + + + +
    + +
    + + +
    + +
    + +
    +
    +
    + + + {/**/} + + + + ) + } +} + +export default Login diff --git a/src/pages/login/Login.less b/src/pages/login/Login.less new file mode 100644 index 0000000..80898fd --- /dev/null +++ b/src/pages/login/Login.less @@ -0,0 +1,125 @@ +@import '~@/utils/utils.less'; +@import '@/global.less'; + +@color: rgba(41, 105, 255, 1); + +.loginBox { + height: 100vh; + + .boxLeft { + background-color: #F5F8FF; + height: 100vh; + position: relative; + + img { + position: absolute; + right: -60px; + bottom: 10%; + max-height: 80%; + max-width: 80%; + } + } + + .boxRight { + height: 100vh; + overflow-y: auto; + + .loginForm { + .formHeader { + width: 100%; + height: 80px; + line-height: 80px; + } + + .formContent { + padding: 40px 0 0; + + .contentBox { + width: 450px; + margin: 0 auto; + + .contentHeader { + margin: 0 auto; + + .headerName { + color: @color; + font-weight: 1000; + font-size: 40px; + } + + @media screen and (max-width: @screen-lg) { + .headerName { font-size: 30px; } + } + + @media screen and (min-width: @screen-xl) { + .headerName { font-size: 40px; } + } + + .headerSlogan { + font-weight: 400; + font-size: 16px; + padding: 16px 0 40px; + } + } + + .contentBody { + margin: 0 auto; + + .username, .password { + margin-bottom: 20px; + + .ant-form-item-label { + font-size: 14px; + padding: 0 0 14px; + letter-spacing: 1px; + } + + .ant-input { + padding: 12px 10px; + font-size: 18px; + border-radius: 5px; + } + + .ant-input-password { + padding: 0 10px 0 0; + } + } + + .isRemember { + padding-top: 10px; + padding-bottom: 24px; + + .ant-checkbox+span { + padding: 0 8px; + letter-spacing: 1px; + } + } + + .loginBtn { + width: 100%; + background-color: @color; + padding: 12px; + height: 54px; + line-height: 1; + border-radius: 5px; + } + + .isForget { + padding-top: 20px; + text-align: center; + + span+a { + padding: 0 8px; + letter-spacing: 1px; + } + + a { + color: @color; + } + } + } + } + } + } + } +} diff --git a/src/pages/login/models/Login.js b/src/pages/login/models/Login.js new file mode 100644 index 0000000..ce86291 --- /dev/null +++ b/src/pages/login/models/Login.js @@ -0,0 +1,42 @@ +import { loginUser, logOutUser } from '@/services/system/api_login' + +export default { + namespace: 'login', + state: { + data: {} + }, + effects: { + *login_user({ payload, callback }, { call, put }) { + const response = yield call(loginUser, payload) + yield put({ + type: 'loginUser', + payload: response + }) + + if (callback) callback(response) + }, + *login_out_user({ payload, callback }, { call, put }) { + const response = yield call(logOutUser, payload) + yield put({ + type: 'logOutUser', + payload: response + }) + + if (callback) callback(response) + } + }, + reducers: { + loginUser(state, { payload }) { + return { + ...state, + data: { ...payload } + } + }, + logOutUser(state, { payload }) { + return { + ...state, + data: { ...payload } + } + } + } +} diff --git a/src/pages/nav_pocmodel_content/PocmodelContentList.js b/src/pages/nav_pocmodel_content/PocmodelContentList.js new file mode 100644 index 0000000..fd010ee --- /dev/null +++ b/src/pages/nav_pocmodel_content/PocmodelContentList.js @@ -0,0 +1,73 @@ +import React, { useState, useEffect } from 'react' +import { history, Outlet, useLocation, matchRoutes } from '@umijs/max' +import { Menu } from 'antd' +import './PocmodelContentList.less' +import { formatRoute, getDefaultRoute } from '@/utils/routeUtils' + +const PocmodelContentList = (props) => { + const dynamicRoute = window.dynamicRoute + const location = useLocation() + const pathName = location.pathname + const [openKey, setOpenKey] = useState([]) + const [selectedKey, setSelectedKey] = useState([]) + const [menuItems, setMenuItems] = useState([]) + let defaultKey = '' + + useEffect(() => { + if (!dynamicRoute || dynamicRoute?.length === 0) return + let newList = [] + let routes = [] + + if (dynamicRoute?.length) { + let tempRoute = dynamicRoute?.filter(item => pathName.indexOf(item.key) !== -1) ?? [] + + newList = formatRoute(tempRoute[0]?.children) + routes = formatRoute(tempRoute[0]?.children, true) + defaultKey = getDefaultRoute(routes) + const mathRoute = matchRoutes(routes, pathName) + + setRouteActive({ key: mathRoute?.length ? pathName : defaultKey }, routes) + } + + setMenuItems(newList) + }, [pathName]) + + const setRouteActive = (value, menu) => { + const curKey = value.key + const tempMenu = menu ?? menuItems ?? [] + const mathRoute = matchRoutes(tempMenu, curKey) + let openKeys = [] + let selectedKeys = [] + + mathRoute?.map((item, index) => { + mathRoute?.length !== index + 1 && (openKeys = [...openKeys, item.pathname]) + mathRoute?.length === index + 1 && (selectedKeys = [...selectedKeys, item.pathname]) + }) + + setOpenKey(openKeys) + setSelectedKey(selectedKeys) + history.replace(curKey) + } + + return ( +
    +
    + setRouteActive(value)} + onOpenChange={value => setOpenKey(value)} + mode='inline' + style={{ width: 200 }} + /> +
    + +
    + +
    +
    + ) +} + +export default PocmodelContentList diff --git a/src/pages/nav_pocmodel_content/PocmodelContentList.less b/src/pages/nav_pocmodel_content/PocmodelContentList.less new file mode 100644 index 0000000..d2ecd56 --- /dev/null +++ b/src/pages/nav_pocmodel_content/PocmodelContentList.less @@ -0,0 +1,27 @@ +.systemContent { + display: flex; + height: 100%; + justify-content: space-between; + flex-wrap: nowrap; + padding: 12px; + + .leftMenu { + margin: 0; + width: 200px; + background-color: #ffffff; + border-radius: 6px; + overflow-y: auto; + overflow-x: hidden; + + .ant-menu-inline { + background: #ffffff !important; + } + } + + .rightContentMain { + width: calc(100% - 212px); + background-color: #ffffff; + border-radius: 6px; + overflow-y: auto; + } +} diff --git a/src/pages/nav_system_content/SystemContentList.js b/src/pages/nav_system_content/SystemContentList.js new file mode 100644 index 0000000..cdf3db9 --- /dev/null +++ b/src/pages/nav_system_content/SystemContentList.js @@ -0,0 +1,402 @@ +import React, { useState, useEffect } from 'react' +import { history, Outlet, useLocation, matchRoutes, useModel } from '@umijs/max' +import { Menu, Tabs, Select } from 'antd' +import './SystemContentList.less' +import { formatRoute, getDefaultRoute } from '@/utils/routeUtils' +import styles from './TopNavBar.less' +import { Row, Col, Avatar, Dropdown, Button } from 'antd' +import { userInfo } from '@/utils/globalCommon' +import { HomeOutlined, LogoutOutlined, AppstoreOutlined, UserOutlined, SettingOutlined, DatabaseOutlined, FileTextOutlined, LockOutlined, AreaChartOutlined, CaretDownOutlined, BellOutlined, SearchOutlined, QuestionCircleOutlined } from '@ant-design/icons' +import { getPageQuery } from '@/utils/utils' +import menuTitle from '@/assets/img/智能管控平台.svg' +import menuTitle1 from '@/assets/img/智能管控平台-1.svg' +import fireHydrant from '@/assets/img/fireHydrant.svg' +import fireHydrant1 from '@/assets/img/fireHydrant1.svg' +import trouble from '@/assets/img/trouble.svg' +import book from '@/assets/img/book.svg' +import danger from '@/assets/img/danger.svg' +import danger1 from '@/assets/img/danger1.svg' +import license from '@/assets/img/license.svg' +import people from '@/assets/img/people.svg' +import risk from '@/assets/img/risk.svg' +import { CustomBreadcrumb } from '@/components/GlobalComponent' + + +// 自定义菜单项渲染组件,支持根据激活状态显示不同图片 +const CustomMenuItem = ({ item, selectedKeys }) => { + const isActive = selectedKeys.includes(item.key); + + // 为每个菜单项定义激活状态的图片 + // 如果没有专门的激活状态图片,则使用默认图片 + const getActiveIcon = (baseIcon) => { + // 这里可以根据需要为每个图标定义对应的激活状态图片 + // 目前暂时使用相同的图片,用户可以根据实际情况替换 + return baseIcon; + }; + + const renderIcon = () => { + if (item.icon && typeof item.icon === 'object' && item.icon.props && item.icon.props.src) { + const iconSrc = isActive ? getActiveIcon(item.icon.props.src) : item.icon.props.src; + return {item.icon.props.alt + } + return item.icon; + }; + + const renderMenuItem = (menuItem) => ({ + ...menuItem, + icon: renderIcon(), + children: menuItem.children?.map(child => renderMenuItem(child)) + }); + + return renderMenuItem(item); +}; + +const SystemContentList = (props) => { + const dynamicRoute = window.dynamicRoute + console.log(dynamicRoute) + const location = useLocation() + const pathName = location.pathname + const [openKey, setOpenKey] = useState([]) + const [selectedKey, setSelectedKey] = useState([]) + const [menuItems, setMenuItems] = useState([]) + const [systemType, setSystemType] = useState('智能巡检系统') + const [isMenuCollapsed, setIsMenuCollapsed] = useState(false); // 添加菜单收缩状态 + let defaultKey = '' + + useEffect(() => { + console.log('dynamicRoute structure:', dynamicRoute) + if (!dynamicRoute || dynamicRoute?.length === 0) return + let newList = [] + let routes = [] + + if (dynamicRoute?.length) { + let tempRoute = dynamicRoute?.filter(item => pathName.indexOf(item.key) !== -1) ?? [] + + newList = formatRoute(tempRoute[0]?.children) + routes = formatRoute(tempRoute[0]?.children, true) + defaultKey = getDefaultRoute(routes) + const mathRoute = matchRoutes(routes, pathName) + + setRouteActive({ key: mathRoute?.length ? pathName : defaultKey }, routes) + } + + setMenuItems(newList) + + const fixedMenuItems = [ + { + "path": "/topnavbar00/business/basicinformation", + icon: 安全管理基础信息, + "key": "/topnavbar00/business/basicinformation", + "label": "安全管理基础信息" + }, + { + "path": "/topnavbar00/business/hiddentrouble", + icon: 工时仪表盘, + "key": "/topnavbar00/business/hiddentrouble", + "label": "隐患排查" + } + ] + setMenuItems(fixedMenuItems) + // 初始化默认路由 + const defaultRoute = fixedMenuItems[0]?.key || '' + const mathRoute = matchRoutes(fixedMenuItems, pathName) + setRouteActive({ key: mathRoute?.length ? pathName : defaultRoute }, fixedMenuItems) + }, [pathName]) + + const setRouteActive = (value, menu) => { + const curKey = value.key + const tempMenu = menu ?? menuItems ?? [] + const mathRoute = matchRoutes(tempMenu, curKey) + let openKeys = [] + let selectedKeys = [] + + mathRoute?.map((item, index) => { + mathRoute?.length !== index + 1 && (openKeys = [...openKeys, item.pathname]) + mathRoute?.length === index + 1 && (selectedKeys = [...selectedKeys, item.pathname]) + }) + + setOpenKey(openKeys) + setSelectedKey(selectedKeys) + history.replace(curKey) + } + + // 切换菜单收缩状态 + const toggleMenu = () => { + setIsMenuCollapsed(!isMenuCollapsed); + }; + + const { initialState: { menu }, setInitialState } = useModel('@@initialState') + + const loginOut = async () => { + // await outLogin() + const { redirect } = getPageQuery() + + if (window.location.pathname !== '/login/login' && !redirect) { + history.replace({ + pathname: '/login', + // search: stringify({ + // redirect: window.location.href, + // }), + }) + + setInitialState({ currentUser: null, menu: null, menuMap: null }) + window.dynamicRoute = null + localStorage.clear() + } + } + + const handleMenuClick = (e) => { + switch (e.key) { + case 'logout': + loginOut() + break + default: + break + } + } + + const dropDownMenuItems = [ + { + label: <>退出登录, + key: 'logout' + } + ] + + const dropDownMenu = () => ( + + ) + + return ( +
    +
    +
    + menuTitle + menuTitle1 +
    +
    +