| | |
| | | import React, {Component} from 'react' |
| | | import PropTypes from 'prop-types' |
| | | import { fromJS } from 'immutable' |
| | | import { Form, Row, Col, Input, Select, Radio, Tooltip, InputNumber } from 'antd' |
| | | import { Form, Row, Col, Input, Select, Radio, Tooltip, InputNumber, Cascader } from 'antd' |
| | | import { QuestionCircleOutlined } from '@ant-design/icons' |
| | | |
| | | import { formRule } from '@/utils/option.js' |
| | |
| | | |
| | | const cardTypeOptions = { |
| | | sequence: ['eleType', 'width'], |
| | | text: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'link', 'noValue', 'bgImage', 'fixStyle'], |
| | | text: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'link', 'anchors', 'noValue', 'bgImage', 'fixStyle'], |
| | | number: ['eleType', 'datatype', 'format', 'width', 'height', 'prefix', 'postfix', 'noValue', 'fixStyle'], |
| | | picture: ['eleType', 'datatype', 'width', 'lenWidRadio', 'maxWidth', 'link', 'noValue'], |
| | | video: ['eleType', 'datatype', 'width', 'aspectRatio', 'autoPlay', 'loop', 'noValue'], |
| | |
| | | </Form.Item> |
| | | </Col> |
| | | ) |
| | | } else if (item.type === 'cascader') { |
| | | fields.push( |
| | | <Col span={12} key={index}> |
| | | <Form.Item label={item.tooltip ? |
| | | <Tooltip placement="topLeft" overlayClassName={item.tooltipClass} title={item.tooltip}> |
| | | <QuestionCircleOutlined className="mk-form-tip" /> |
| | | {item.label} |
| | | </Tooltip> : item.label |
| | | }> |
| | | {getFieldDecorator(item.key, { |
| | | initialValue: item.initVal || [], |
| | | rules: [ |
| | | { |
| | | required: !!item.required, |
| | | message: this.props.dict['form.required.select'] + item.label + '!' |
| | | } |
| | | ] |
| | | })( |
| | | <Cascader options={item.options || []} expandTrigger="hover" placeholder=""/> |
| | | )} |
| | | </Form.Item> |
| | | </Col> |
| | | ) |
| | | } |
| | | }) |
| | | return fields |
| | |
| | | * @param {*} card |
| | | * @param {*} type |
| | | */ |
| | | export function getCardCellForm (card, type, subtype, cardCell) { |
| | | export function getCardCellForm (card, type, subtype, cardCell, anchors) { |
| | | let _options = [ |
| | | { value: 'text', text: '文本'}, |
| | | { value: 'number', text: '数值'}, |
| | |
| | | options: [] |
| | | }, |
| | | { |
| | | type: 'cascader', |
| | | key: 'anchors', |
| | | label: '跳转锚点', |
| | | initVal: card.anchors || [], |
| | | required: false, |
| | | options: anchors |
| | | }, |
| | | { |
| | | type: 'number', |
| | | key: 'barHeight', |
| | | min: 5, |
| | |
| | | type: 'number', |
| | | key: 'fixSize', |
| | | min: 10, |
| | | max: 100, |
| | | max: 300, |
| | | label: '字体大小', |
| | | initVal: card.fixSize || 14, |
| | | tooltip: '前缀、后缀的字体大小。', |
| | |
| | | if (card.eleType === 'button') { |
| | | this.handleAction(card) |
| | | } else { |
| | | let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, cards.uuid) || [] |
| | | |
| | | this.setState({ |
| | | visible: true, |
| | | card: card, |
| | | formlist: getCardCellForm(card, cards.type, cards.subtype, cardCell) |
| | | formlist: getCardCellForm(card, cards.type, cards.subtype, cardCell, anchors) |
| | | }) |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | let modules = MenuUtils.getSubModules(window.GLOB.customMenu.components, cards.uuid) || [] |
| | | let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, cards.uuid) || [] |
| | | |
| | | this.setState({ |
| | | actvisible: true, |
| | | card: card, |
| | | formlist: getActionForm(card, functip, cards, usefulFields, 'card', menulist, modules) |
| | | formlist: getActionForm(card, functip, cards, usefulFields, 'card', menulist, modules, anchors) |
| | | }) |
| | | } |
| | | |
| | |
| | | label: '字体大小', |
| | | initVal: card.fontSize || 28, |
| | | min: 12, |
| | | max: 200, |
| | | max: 300, |
| | | decimal: 0, |
| | | required: true |
| | | }, |
| | |
| | | } else if (card.type === 'next') { |
| | | return ['type', 'label', 'enable'] |
| | | } |
| | | let _options = ['type', 'label', 'intertype', 'syncComponent', 'linkmenu', 'open', 'enable', 'output', 'reload'] // 选项列表 |
| | | let _options = ['type', 'label', 'intertype', 'syncComponent', 'anchors', 'linkmenu', 'open', 'enable', 'output', 'reload'] // 选项列表 |
| | | |
| | | if (_intertype === 'custom') { |
| | | _options.pop() |
| | |
| | | * @param {*} type 按钮类型,用于区分可选的打开方式 |
| | | */ |
| | | |
| | | export function getActionForm (card, functip, tableName, usefulFields, modules) { |
| | | export function getActionForm (card, functip, tableName, usefulFields, modules, anchors) { |
| | | const appType = sessionStorage.getItem('appType') |
| | | let _type = '提交' |
| | | if (card.type === 'prev') { |
| | |
| | | options: modules |
| | | }, |
| | | { |
| | | type: 'cascader', |
| | | key: 'anchors', |
| | | label: '跳转锚点', |
| | | initVal: card.anchors || [], |
| | | tooltip: '执行成功后,需要跳转的锚点', |
| | | required: false, |
| | | options: anchors |
| | | }, |
| | | { |
| | | type: 'radio', |
| | | key: 'reload', |
| | | label: '上一页', |
| | |
| | | </div> |
| | | |
| | | let modules = MenuUtils.getSubModules(window.GLOB.customMenu.components, config.uuid) || [] |
| | | let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, config.uuid) || [] |
| | | |
| | | this.setState({ |
| | | visible: true, |
| | | card: card, |
| | | formlist: getActionForm(card, functip, config.setting.tableName, usefulFields, modules) |
| | | formlist: getActionForm(card, functip, config.setting.tableName, usefulFields, modules, anchors) |
| | | }) |
| | | } |
| | | |
| | |
| | | const { TextArea } = Input |
| | | const MkEditIcon = asyncComponent(() => import('@/components/mkIcon')) |
| | | const acTyOptions = { |
| | | pop: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle', 'hidden'], |
| | | prompt: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle', 'hidden'], |
| | | exec: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'openmenu', 'open', 'refreshTab', 'hidden'], |
| | | pop: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'anchors', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle', 'hidden'], |
| | | prompt: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'anchors', 'width', 'openmenu', 'open', 'refreshTab', 'position', 'tipTitle', 'hidden'], |
| | | exec: ['label', 'OpenType', 'intertype', 'Ot', 'show', 'swipe', 'icon', 'class', 'color', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'anchors', 'width', 'openmenu', 'open', 'refreshTab', 'hidden'], |
| | | excelIn: ['label', 'Ot', 'OpenType', 'intertype', 'show', 'icon', 'class', 'color', 'sheet', 'execSuccess', 'execError', 'resetPageIndex', 'syncComponent', 'switchTab', 'width', 'hidden'], |
| | | excelOut: ['label', 'OpenType', 'intertype', 'show', 'icon', 'class', 'color', 'execSuccess', 'execError', 'syncComponent', 'switchTab', 'resetPageIndex', 'pagination', 'search', 'width', 'hidden'], |
| | | popview: ['label', 'Ot', 'OpenType', 'show', 'icon', 'class', 'color', 'popClose', 'resetPageIndex', 'width', 'display', 'ratio', 'syncComponent', 'clickouter', 'hidden'], |
| | |
| | | * @param {*} usefulFields 存储过程可用的开始字段 |
| | | * @param {*} type 按钮类型,用于区分可选的打开方式 |
| | | */ |
| | | export function getActionForm (card, functip, config, usefulFields, type, menulist = [], modules = []) { |
| | | export function getActionForm (card, functip, config, usefulFields, type, menulist = [], modules = [], anchors = []) { |
| | | let appType = sessionStorage.getItem('appType') |
| | | let viewType = sessionStorage.getItem('editMenuType') // 弹窗 popview |
| | | let printTemps = sessionStorage.getItem('printTemps') |
| | |
| | | }, |
| | | { |
| | | type: 'cascader', |
| | | key: 'anchors', |
| | | label: '跳转锚点', |
| | | initVal: card.anchors || [], |
| | | tooltip: '执行成功后,需要跳转的锚点', |
| | | required: false, |
| | | options: anchors |
| | | }, |
| | | { |
| | | type: 'cascader', |
| | | key: 'refreshTab', |
| | | label: '刷新菜单', |
| | | initVal: card.refreshTab || [], |
| | |
| | | precision: 0, |
| | | label: '比例', |
| | | initVal: card.ratio || 85, |
| | | tooltip: '小于100为宽度(或高度)百分比,大于100为像素值。', |
| | | tooltip: '模态框或抽屉的宽度,小于100为窗口宽度(或高度)百分比,大于100为像素值。', |
| | | required: true |
| | | }, |
| | | { |
| | |
| | | } |
| | | |
| | | let modules = MenuUtils.getSubModules(window.GLOB.customMenu.components, config.uuid) || [] |
| | | let anchors = MenuUtils.getAnchors(window.GLOB.customMenu.components, config.uuid) || [] |
| | | |
| | | this.setState({ |
| | | visible: true, |
| | | card: card, |
| | | formlist: getActionForm(card, functip, config, usefulFields, this.props.type, menulist, modules) |
| | | formlist: getActionForm(card, functip, config, usefulFields, this.props.type, menulist, modules, anchors) |
| | | }) |
| | | } |
| | | |
| | |
| | | {options.includes('font') ? <Panel header="字体" key="font"> |
| | | <Col span={12}> |
| | | <Form.Item colon={false} label={<FontSizeOutlined title="字体大小"/>}> |
| | | <InputNumber defaultValue={card.fontSize || 14} min={12} max={100} precision={0} onChange={this.changeFontSize} /> |
| | | <InputNumber defaultValue={card.fontSize || 14} min={12} max={300} precision={0} onChange={this.changeFontSize} /> |
| | | </Form.Item> |
| | | </Col> |
| | | <Col span={12}> |
| | |
| | | label: '字体大小', |
| | | initval: setting.iconFont || 20, |
| | | min: 12, |
| | | max: 200, |
| | | max: 300, |
| | | precision: 0, |
| | | required: true |
| | | }, |
| | |
| | | openNewView = (e, card) => { |
| | | const { cardCell, data, cards } = this.props |
| | | |
| | | if (data.$disabled) return |
| | | |
| | | if (card.anchors && card.anchors.length > 0) { |
| | | let id = card.anchors[card.anchors.length - 1] |
| | | let node = document.getElementById('anchor' + id) |
| | | node && node.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'}) |
| | | } |
| | | |
| | | if (!card.link) return |
| | | e.stopPropagation() |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | if (card.link) { |
| | | if (card.link || (card.anchors && card.anchors.length > 0)) { |
| | | _style.cursor = 'pointer' |
| | | } |
| | | |
| | | if (!card.link) |
| | | |
| | | if (card.bgImage && data[card.bgImage]) { |
| | | _style.backgroundImage = `url('${data[card.bgImage]}')` |
| | | } |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className="custom-data-card-box" style={config.style}> |
| | | <div className="custom-data-card-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | {data ? <div className="ant-spin-blur"></div> : null} |
| | |
| | | if (config.wrap.empty === 'hidden' && (!data || data.$$empty)) return null |
| | | |
| | | return ( |
| | | <div className="custom-prop-card-box" style={config.style}> |
| | | <div className="custom-prop-card-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | if (config.wrap.empty === 'hidden' && (!data || data.length === 0)) return null |
| | | |
| | | return ( |
| | | <div className="custom-table-card-box" style={{...config.style}}> |
| | | <div className="custom-table-card-box" id={'anchor' + config.uuid} style={{...config.style}}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | {data ? <div className="ant-spin-blur"></div> : null} |
| | |
| | | if (config.wrap.empty === 'hidden' && (!data || data.length === 0)) return null |
| | | |
| | | return ( |
| | | <div className="custom-data-carousel-box" style={config.style}> |
| | | <div className="custom-data-carousel-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | {data ? <div className="ant-spin-blur"></div> : null} |
| | |
| | | if (config.wrap.empty === 'hidden' && (!data || data.$$empty)) return null |
| | | |
| | | return ( |
| | | <div className="custom-prop-carousel-box" style={config.style}> |
| | | <div className="custom-prop-carousel-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className="custom-line-chart-plot-box" style={style}> |
| | | <div className="custom-line-chart-plot-box" id={'anchor' + config.uuid} style={style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { config, loading } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-dashboard-plot-box" style={config.style}> |
| | | <div className="custom-dashboard-plot-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className="custom-pie-chart-plot-box" style={style}> |
| | | <div className="custom-pie-chart-plot-box" id={'anchor' + config.uuid} style={style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className="custom-scatter-plot-box" style={style}> |
| | | <div className="custom-scatter-plot-box" id={'anchor' + config.uuid} style={style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { config, loading, empty, BID } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-chart-plot-box" style={config.style}> |
| | | <div className="custom-chart-plot-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { config, loading, html } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-sand-box" style={{...config.style}}> |
| | | <div className="custom-sand-box" id={'anchor' + config.uuid} style={{...config.style}}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { config, loading, data } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-braft-editor-box" style={config.style}> |
| | | <div className="custom-braft-editor-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { config, loading, BID, data, group, dict, step } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-normal-form-box" style={{...config.style}}> |
| | | <div className="custom-normal-form-box" id={'anchor' + config.uuid} style={{...config.style}}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { config, loading, BID, data, group, dict } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-tab-form-box" style={{...config.style}}> |
| | | <div className="custom-tab-form-box" id={'anchor' + config.uuid} style={{...config.style}}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | const { BID, setting, searchlist, actions, config, columns, BData, selectedData, lock } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-edit-table" style={config.style}> |
| | | <div className="custom-edit-table" id={'anchor' + config.uuid} style={config.style}> |
| | | <NormalHeader config={config}/> |
| | | {searchlist && searchlist.length ? |
| | | <MainSearch BID={BID} setting={config.wrap} searchlist={searchlist} menuType={this.props.menuType} refreshdata={this.refreshbysearch}/> : null |
| | |
| | | } |
| | | |
| | | return ( |
| | | <div className="custom-normal-table" style={style}> |
| | | <div className="custom-normal-table" id={'anchor' + config.uuid} style={style}> |
| | | {config.wrap.collapse === 'true' ? <Collapse bordered={false} defaultActiveKey="1" expandIconPosition="right"> |
| | | <Panel forceRender={true} header={<NormalHeader config={config}/>} key="1"> |
| | | {searchlist && searchlist.length ? |
| | |
| | | const { config, loading, data } = this.state |
| | | |
| | | return ( |
| | | <div className="normal-timeline-box" style={{...config.style}}> |
| | | <div className="normal-timeline-box" id={'anchor' + config.uuid} style={{...config.style}}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | {data ? <div className="ant-spin-blur"></div> : null} |
| | |
| | | const { config, loading, treeNodes, expandedKeys, selectedKeys } = this.state |
| | | |
| | | return ( |
| | | <div className="custom-tree-box" style={config.style}> |
| | | <div className="custom-tree-box" id={'anchor' + config.uuid} style={config.style}> |
| | | {loading ? |
| | | <div className="loading-mask"> |
| | | <div className="ant-spin-blur"></div> |
| | |
| | | let node = document.getElementById('tab' + id) |
| | | node && node.click() |
| | | } |
| | | if (btn.anchors && btn.anchors.length > 0) { |
| | | let id = btn.anchors[btn.anchors.length - 1] |
| | | let node = document.getElementById('anchor' + id) |
| | | node && node.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'nearest'}) |
| | | } |
| | | |
| | | if (btn.openmenu && btn.openmenu.length > 0 && btn.MenuID) { |
| | | let newtab = { |
New file |
| | |
| | | import React, {Component} from 'react' |
| | | import PropTypes from 'prop-types' |
| | | import { is, fromJS } from 'immutable' |
| | | import moment from 'moment' |
| | | import { Upload, Button, Progress, notification } from 'antd' |
| | | import { UploadOutlined } from '@ant-design/icons' |
| | | import SparkMD5 from 'spark-md5' |
| | | import Api from '@/api' |
| | | import './index.scss' |
| | | |
| | | let service = '' |
| | | if (process.env.NODE_ENV === 'production') { |
| | | service = document.location.origin + '/' + window.GLOB.service |
| | | } else { |
| | | service = window.GLOB.location + '/' + window.GLOB.service |
| | | } |
| | | |
| | | class FileUpload extends Component { |
| | | static propTpyes = { |
| | | config: PropTypes.object, // 表单信息 |
| | | onChange: PropTypes.func, // 表单变化 |
| | | } |
| | | |
| | | state = { |
| | | percent: 0, |
| | | accept: '', |
| | | accepts: null, |
| | | maxFile: null, |
| | | rduri: '', |
| | | limit: 2, |
| | | compress: false, |
| | | fileType: 'text', |
| | | showprogress: false, |
| | | filelist: [] |
| | | } |
| | | |
| | | UNSAFE_componentWillMount () { |
| | | const { config } = this.props |
| | | |
| | | let filelist = [] |
| | | if (config.initval) { |
| | | if (/^data:image/.test(config.initval)) { |
| | | filelist = [{ |
| | | uid: '0', |
| | | name: 'data:image/jpeg;base64', |
| | | status: 'done', |
| | | url: config.initval, |
| | | origin: true |
| | | }] |
| | | } else { |
| | | try { |
| | | filelist = config.initval.split(',').map((url, index) => { |
| | | return { |
| | | uid: `${index}`, |
| | | name: url.slice(url.lastIndexOf('/') + 1), |
| | | status: 'done', |
| | | url: url, |
| | | origin: true |
| | | } |
| | | }) |
| | | } catch (e) { |
| | | filelist = [] |
| | | } |
| | | } |
| | | } |
| | | |
| | | let accept = '' |
| | | let accepts = null |
| | | let compress = false |
| | | let maxFile = config.maxfile && config.maxfile > 0 ? config.maxfile : null |
| | | if (config.compress === 'true' || config.compress === 'base64') { |
| | | compress = true |
| | | accepts = ['.jpg', '.png', '.gif', '.jpeg'] |
| | | accept = accepts.join(',') |
| | | if (config.compress === 'base64') { |
| | | maxFile = 1 |
| | | } |
| | | } else if (config.suffix) { |
| | | accepts = config.suffix.split(',').map(item => { |
| | | if (!/^\./ig.test(item)) { |
| | | item = '.' + item |
| | | } |
| | | return item |
| | | }) |
| | | accept = accepts.join(',') |
| | | } |
| | | let rduri = config.rduri || '' |
| | | |
| | | if (window.GLOB.systemType === 'production') { |
| | | rduri = config.proRduri || '' |
| | | } |
| | | |
| | | this.setState({ |
| | | rduri, |
| | | accept, |
| | | accepts, |
| | | filelist, |
| | | compress, |
| | | limit: config.limit || 2, |
| | | maxFile: maxFile, |
| | | fileType: config.fileType || 'text' |
| | | }) |
| | | } |
| | | |
| | | shouldComponentUpdate (nextProps, nextState) { |
| | | return !is(fromJS(this.state), fromJS(nextState)) |
| | | } |
| | | |
| | | onChange = ({ fileList }) => { |
| | | fileList = fileList.map(item => { |
| | | if (item.status === 'error' && /^<!DOCTYPE html>/.test(item.response)) { |
| | | item.response = '' |
| | | } |
| | | return item |
| | | }) |
| | | |
| | | this.setState({filelist: fileList}) |
| | | } |
| | | |
| | | onRemove = file => { |
| | | const files = this.state.filelist.filter(v => v.uid !== file.uid) |
| | | |
| | | this.setState({filelist: files}) |
| | | |
| | | let vals = [] |
| | | |
| | | files.forEach(item => { |
| | | if (item.origin && item.url) { |
| | | vals.push(item.url) |
| | | } else if (!item.origin && item.status === 'done' && item.response) { |
| | | vals.push(item.response) |
| | | } |
| | | }) |
| | | |
| | | this.props.onChange(vals.join(',')) |
| | | } |
| | | |
| | | onUpdate = (url) => { |
| | | let filelist = fromJS(this.state.filelist).toJS() |
| | | |
| | | if (filelist[filelist.length -1]) { |
| | | filelist[filelist.length -1].status = 'done' |
| | | filelist[filelist.length -1].response = url |
| | | filelist[filelist.length -1].origin = false |
| | | } |
| | | |
| | | filelist = filelist.filter(item => !!(item.url || item.response)) |
| | | |
| | | let vals = [] |
| | | |
| | | filelist.forEach(item => { |
| | | if (item.origin && item.url) { |
| | | vals.push(item.url) |
| | | } else if (!item.origin && item.status === 'done' && item.response) { |
| | | vals.push(item.response) |
| | | } |
| | | }) |
| | | |
| | | this.setState({filelist}) |
| | | this.props.onChange(vals.join(',')) |
| | | } |
| | | |
| | | onFail = (msg) => { |
| | | let filelist = this.state.filelist.map(item => { |
| | | if (!item.url && !item.response && !item.status) { |
| | | item.status = 'error' |
| | | } |
| | | return item |
| | | }) |
| | | |
| | | this.setState({filelist, showprogress: false, percent: 0}) |
| | | |
| | | notification.warning({ |
| | | top: 92, |
| | | message: msg || '文件上传失败!', |
| | | duration: 5 |
| | | }) |
| | | } |
| | | |
| | | shardupload = (params) => { |
| | | let param = params.chunks.shift() |
| | | let form = new FormData() |
| | | |
| | | form.append('file', param.binary) |
| | | form.append('fileMd5', params.file.fileMd5) |
| | | form.append('shardingMd5', param.chunkMd5) |
| | | form.append('baseDomain', service) |
| | | form.append('rootPath', 'Content/images/upload/') |
| | | form.append('fileName', params.file.fileName) |
| | | form.append('fileExt', params.file.fileType) |
| | | form.append('shardingCnt', param.chunks) |
| | | form.append('shardingNo', param.chunk) |
| | | form.append('LoginUID', sessionStorage.getItem('LoginUID') || '') |
| | | form.append('UserID', sessionStorage.getItem('UserID') || '') |
| | | |
| | | Api.getLargeFileUpload(form).then(res => { |
| | | if (res.status) { |
| | | if (params.chunks.length > 0) { |
| | | this.setState({ |
| | | percent: Math.floor(100 * (param.chunk / param.chunks)) |
| | | }) |
| | | this.shardupload(params) |
| | | } else { |
| | | if (res.urlPath) { |
| | | this.onUpdate(res.urlPath) |
| | | } else { |
| | | this.onFail() |
| | | } |
| | | this.setState({ |
| | | percent: 100 |
| | | }, () => { |
| | | setTimeout(() => { |
| | | this.setState({ |
| | | showprogress: false, |
| | | percent: 0 |
| | | }) |
| | | }, 200) |
| | | }) |
| | | } |
| | | } else { |
| | | this.onFail(res.message) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | getuuid = () => { |
| | | let uuid = [] |
| | | let _options = '0123456789abcdefghigklmnopqrstuv' |
| | | for (let i = 0; i < 19; i++) { |
| | | uuid.push(_options.substr(Math.floor(Math.random() * 0x20), 1)) |
| | | } |
| | | uuid = uuid.join('') |
| | | return uuid |
| | | } |
| | | |
| | | beforeUpload = (file) => { |
| | | const { accepts, compress, limit, rduri } = this.state |
| | | |
| | | if (accepts && file.name) { |
| | | let pass = false |
| | | accepts.forEach(type => { |
| | | if (new RegExp(type + '$', 'ig').test(file.name)) { |
| | | pass = true |
| | | } |
| | | }) |
| | | |
| | | if (!pass) { |
| | | setTimeout(() => { |
| | | this.onFail('文件格式错误!') |
| | | }, 10) |
| | | return false |
| | | } |
| | | } |
| | | |
| | | this.setState({ |
| | | showprogress: true, |
| | | percent: 0 |
| | | }) |
| | | |
| | | if (compress) { |
| | | let reader = new FileReader() |
| | | let fileSize = file.size / 1024 / 1024 |
| | | let compressRate = 0.9 |
| | | |
| | | if (fileSize / limit > 5) { |
| | | compressRate = 0.4 |
| | | } else if (fileSize / limit > 4) { |
| | | compressRate = 0.5 |
| | | } else if (fileSize / limit > 3) { |
| | | compressRate = 0.6 |
| | | } else if (fileSize / limit > 2) { |
| | | compressRate = 0.7 |
| | | } else if (fileSize > limit) { |
| | | compressRate = 0.8 |
| | | } |
| | | |
| | | reader.onload = (e) => { |
| | | let img = new Image() |
| | | let maxW = 640 |
| | | |
| | | img.onload = () => { |
| | | let cvs = document.createElement( 'canvas') |
| | | let ctx = cvs.getContext( '2d') |
| | | |
| | | if (img.width > maxW) { |
| | | img.height *= maxW / img.width |
| | | img.width = maxW |
| | | } |
| | | |
| | | cvs.width = img.width |
| | | cvs.height = img.height |
| | | |
| | | ctx.clearRect(0, 0, cvs.width, cvs.height) |
| | | ctx.drawImage(img, 0, 0, img.width, img.height) |
| | | |
| | | let param = {Base64Img: cvs.toDataURL('image/jpeg', compressRate)} |
| | | |
| | | if (this.props.config.compress === 'base64') { |
| | | this.onUpdate(param.Base64Img) |
| | | |
| | | this.setState({ |
| | | percent: 100 |
| | | }, () => { |
| | | setTimeout(() => { |
| | | this.setState({ |
| | | showprogress: false, |
| | | percent: 0 |
| | | }) |
| | | }, 200) |
| | | }) |
| | | } else { |
| | | if (rduri) { |
| | | param.rduri = rduri |
| | | } |
| | | |
| | | Api.fileuploadbase64(param).then(result => { |
| | | if (result.status && result.Images) { |
| | | let url = service + result.Images |
| | | |
| | | if (rduri) { |
| | | url = rduri.replace(/webapi(.*)$/, '') + result.Images |
| | | } |
| | | |
| | | this.onUpdate(url) |
| | | |
| | | this.setState({ |
| | | percent: 100 |
| | | }, () => { |
| | | setTimeout(() => { |
| | | this.setState({ |
| | | showprogress: false, |
| | | percent: 0 |
| | | }) |
| | | }, 200) |
| | | }) |
| | | } else { |
| | | this.onFail(result.message) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | |
| | | img.onerror = () => { |
| | | this.onFail('图片读取失败!') |
| | | } |
| | | |
| | | img.src = e.target.result |
| | | } |
| | | |
| | | reader.onerror = () => { |
| | | this.onFail('文件读取失败!') |
| | | } |
| | | |
| | | reader.readAsDataURL(file) |
| | | return false |
| | | } |
| | | |
| | | // 兼容性的处理 |
| | | let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice |
| | | let chunkSize = 1024 * 1024 * 2 // 切片每次2M |
| | | let chunks = Math.ceil(file.size / chunkSize) // 切片总数 |
| | | let currentChunk = 0 // 当前上传的chunk |
| | | let spark = new SparkMD5.ArrayBuffer() // 对arrayBuffer数据进行md5加密,产生一个md5字符串 |
| | | let chunkFileReader = new FileReader() // 用于计算出每个chunkMd5 |
| | | let totalFileReader = new FileReader() // 用于计算出总文件的fileMd5 |
| | | let params = {chunks: [], file: {}} // 用于上传所有分片的md5信息 |
| | | |
| | | params.file.fileName = file.name.replace(/\.{1}[^.]*$/ig, '') // 文件名(去除后缀名) |
| | | params.file.fileType = file.name.replace(/^.*\.{1}/ig, '') // 文件类型 |
| | | params.file.fileSize = file.size // 文件大小 |
| | | params.file.fileChunks = chunks // 记录所有chunks的长度 |
| | | |
| | | if (!/^[A-Za-z0-9]+$/.test(params.file.fileName)) { // 文件名称含有英文及数字之外字符时,名称系统生成 |
| | | params.file.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid() |
| | | } |
| | | |
| | | totalFileReader.readAsArrayBuffer(file) |
| | | totalFileReader.onload = (e) => { // 对整个totalFile生成md5 |
| | | spark.append(e.target.result) |
| | | params.file.fileMd5 = spark.end() // 计算整个文件的fileMd5 |
| | | |
| | | let _param = new FormData() |
| | | _param.append('fileMd5', params.file.fileMd5) |
| | | |
| | | Api.getFilePreUpload(_param).then(res => { |
| | | if (res.status && res.urlPath) { |
| | | this.onUpdate(res.urlPath) |
| | | this.setState({ |
| | | percent: 100 |
| | | }, () => { |
| | | setTimeout(() => { |
| | | this.setState({ |
| | | showprogress: false, |
| | | percent: 0 |
| | | }) |
| | | }, 200) |
| | | }) |
| | | } else if (res.shardings && res.shardings.length > 0) { |
| | | res.shardings.forEach(shard => { |
| | | if (shard.shardingNo && parseInt(shard.shardingNo) > currentChunk) { |
| | | currentChunk = parseInt(shard.shardingNo) |
| | | } |
| | | }) |
| | | loadNext() |
| | | } else { |
| | | loadNext() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | chunkFileReader.onload = (e) => { |
| | | spark.append(e.target.result) // 对每一片分片进行md5加密 |
| | | |
| | | params.chunks[params.chunks.length - 1].chunkMd5 = spark.end() // 添加切片md5 |
| | | |
| | | currentChunk++ // 每一次分片onload,currentChunk都需要增加,以便来计算分片的次数 |
| | | |
| | | if (currentChunk < chunks) { // 当前切片总数没有达到总数时 |
| | | loadNext() |
| | | } else { |
| | | this.shardupload(params) |
| | | } |
| | | } |
| | | |
| | | chunkFileReader.onerror = () => { |
| | | this.onFail('文件读取失败!') |
| | | } |
| | | totalFileReader.onerror = () => { |
| | | this.onFail('文件读取失败!') |
| | | } |
| | | |
| | | let loadNext = () => { |
| | | let start = currentChunk * chunkSize // 计算分片的起始位置 |
| | | let end = Math.min(file.size, start + chunkSize) // 计算分片的结束位置 |
| | | |
| | | let obj = { // 每一个分片需要包含的信息 |
| | | chunk: currentChunk + 1, |
| | | binary: file.slice(start, end), |
| | | start: start, |
| | | end: end, |
| | | chunks |
| | | } |
| | | |
| | | params.chunks.push(obj) |
| | | chunkFileReader.readAsArrayBuffer(blobSlice.call(file, start, end)) |
| | | } |
| | | |
| | | return false |
| | | } |
| | | |
| | | /** |
| | | * @description 组件销毁,清除state更新 |
| | | */ |
| | | componentWillUnmount () { |
| | | this.setState = () => { |
| | | return |
| | | } |
| | | } |
| | | |
| | | render() { |
| | | const { showprogress, percent, filelist, maxFile, fileType, accept } = this.state |
| | | |
| | | let uploadable = 'fileupload-form-container ' |
| | | |
| | | if (maxFile && filelist.length >= maxFile) { |
| | | uploadable += 'limit-fileupload' |
| | | } |
| | | |
| | | const props = { |
| | | name: 'file', |
| | | disabled: showprogress, |
| | | listType: fileType, |
| | | fileList: filelist, |
| | | action: null, |
| | | accept: accept, |
| | | method: 'post', |
| | | multiple: false, |
| | | onChange: this.onChange, |
| | | onRemove: this.onRemove, |
| | | beforeUpload: this.beforeUpload, |
| | | className: uploadable |
| | | } |
| | | |
| | | return ( |
| | | <Upload {...props}> |
| | | {fileType !== 'picture-card' ? <Button> |
| | | <UploadOutlined /> 点击上传 |
| | | </Button> : null} |
| | | {fileType === 'picture-card' ? <span style={{whiteSpace: 'nowrap'}}> |
| | | <UploadOutlined /> 点击上传 |
| | | </span> : null} |
| | | {showprogress ? <Progress percent={percent} size="small" /> : null} |
| | | </Upload> |
| | | ) |
| | | } |
| | | } |
| | | |
| | | export default FileUpload |
New file |
| | |
| | | .fileupload-form-container { |
| | | .ant-progress-small.ant-progress-line { |
| | | position: absolute; |
| | | bottom: -20px; |
| | | left: 0px; |
| | | } |
| | | |
| | | .ant-upload-select-picture-card { |
| | | .ant-progress-small.ant-progress-line { |
| | | bottom: 0px; |
| | | } |
| | | } |
| | | .ant-upload-list-picture-card-container { |
| | | width: 90px; |
| | | height: 90px; |
| | | } |
| | | // .ant-upload-list-picture-card .ant-upload-list-item-undefined { |
| | | // border-color: #f5222d; |
| | | // .ant-upload-list-item-name { |
| | | // color: #f5222d; |
| | | // } |
| | | // } |
| | | .ant-upload-list-picture-card .ant-upload-list-item { |
| | | width: 90px; |
| | | height: 90px; |
| | | } |
| | | .ant-upload.ant-upload-select-picture-card { |
| | | width: 90px; |
| | | height: 90px; |
| | | } |
| | | a[href^="data"] { |
| | | pointer-events: none; |
| | | .anticon-eye-o { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | .fileupload-form-container.limit-fileupload { |
| | | > .ant-upload { |
| | | display: inline; |
| | | >.ant-upload { |
| | | >input { |
| | | display: none; |
| | | } |
| | | >button { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | > .ant-upload-select-picture-card { |
| | | display: none; |
| | | } |
| | | } |
| | |
| | | }) |
| | | } |
| | | |
| | | shardupload = (params) => { |
| | | let param = params.chunks.shift() |
| | | shardupload = (param) => { |
| | | let form = new FormData() |
| | | |
| | | form.append('file', param.binary) |
| | | form.append('fileMd5', params.file.fileMd5) |
| | | form.append('shardingMd5', param.chunkMd5) |
| | | form.append('fileMd5', param.fileMd5) |
| | | form.append('shardingMd5', param.fileMd5) |
| | | form.append('baseDomain', service) |
| | | form.append('rootPath', 'Content/images/upload/') |
| | | form.append('fileName', params.file.fileName) |
| | | form.append('fileExt', params.file.fileType) |
| | | form.append('shardingCnt', param.chunks) |
| | | form.append('shardingNo', param.chunk) |
| | | form.append('fileName', param.fileName) |
| | | form.append('fileExt', param.fileType) |
| | | form.append('shardingCnt', 1) |
| | | form.append('shardingNo', 1) |
| | | form.append('LoginUID', sessionStorage.getItem('LoginUID') || '') |
| | | form.append('UserID', sessionStorage.getItem('UserID') || '') |
| | | |
| | | Api.getLargeFileUpload(form).then(res => { |
| | | if (res.status) { |
| | | if (params.chunks.length > 0) { |
| | | this.setState({ |
| | | percent: Math.floor(100 * (param.chunk / param.chunks)) |
| | | }) |
| | | this.shardupload(params) |
| | | } else { |
| | | if (res.urlPath) { |
| | | this.onUpdate(res.urlPath) |
| | | } else { |
| | |
| | | }) |
| | | }, 200) |
| | | }) |
| | | } |
| | | } else { |
| | | this.onFail(res.message) |
| | | } |
| | |
| | | } |
| | | |
| | | // 兼容性的处理 |
| | | let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice |
| | | let chunkSize = 1024 * 1024 * 2 // 切片每次2M |
| | | let chunks = Math.ceil(file.size / chunkSize) // 切片总数 |
| | | let currentChunk = 0 // 当前上传的chunk |
| | | let spark = new SparkMD5.ArrayBuffer() // 对arrayBuffer数据进行md5加密,产生一个md5字符串 |
| | | let chunkFileReader = new FileReader() // 用于计算出每个chunkMd5 |
| | | let totalFileReader = new FileReader() // 用于计算出总文件的fileMd5 |
| | | let params = {chunks: [], file: {}} // 用于上传所有分片的md5信息 |
| | | let param = {} |
| | | |
| | | params.file.fileName = file.name.replace(/\.{1}[^.]*$/ig, '') // 文件名(去除后缀名) |
| | | params.file.fileType = file.name.replace(/^.*\.{1}/ig, '') // 文件类型 |
| | | params.file.fileSize = file.size // 文件大小 |
| | | params.file.fileChunks = chunks // 记录所有chunks的长度 |
| | | param.fileName = file.name.replace(/\.{1}[^.]*$/ig, '') // 文件名(去除后缀名) |
| | | param.fileType = file.name.replace(/^.*\.{1}/ig, '') // 文件类型 |
| | | |
| | | if (!/^[A-Za-z0-9]+$/.test(params.file.fileName)) { // 文件名称含有英文及数字之外字符时,名称系统生成 |
| | | params.file.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid() |
| | | if (!/^[A-Za-z0-9]+$/.test(param.fileName)) { // 文件名称含有英文及数字之外字符时,名称系统生成 |
| | | param.fileName = moment().format('YYYYMMDDHHmmss') + this.getuuid() |
| | | } |
| | | |
| | | totalFileReader.readAsArrayBuffer(file) |
| | | totalFileReader.onload = (e) => { // 对整个totalFile生成md5 |
| | | spark.append(e.target.result) |
| | | params.file.fileMd5 = spark.end() // 计算整个文件的fileMd5 |
| | | param.fileMd5 = spark.end() // 计算整个文件的fileMd5 |
| | | param.binary = file |
| | | |
| | | let _param = new FormData() |
| | | _param.append('fileMd5', params.file.fileMd5) |
| | | _param.append('fileMd5', param.fileMd5) |
| | | |
| | | Api.getFilePreUpload(_param).then(res => { |
| | | if (res.status && res.urlPath) { |
| | |
| | | }) |
| | | }, 200) |
| | | }) |
| | | } else if (res.shardings && res.shardings.length > 0) { |
| | | res.shardings.forEach(shard => { |
| | | if (shard.shardingNo && parseInt(shard.shardingNo) > currentChunk) { |
| | | currentChunk = parseInt(shard.shardingNo) |
| | | } |
| | | }) |
| | | loadNext() |
| | | } else { |
| | | loadNext() |
| | | this.shardupload(param) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | chunkFileReader.onload = (e) => { |
| | | spark.append(e.target.result) // 对每一片分片进行md5加密 |
| | | |
| | | params.chunks[params.chunks.length - 1].chunkMd5 = spark.end() // 添加切片md5 |
| | | |
| | | currentChunk++ // 每一次分片onload,currentChunk都需要增加,以便来计算分片的次数 |
| | | |
| | | if (currentChunk < chunks) { // 当前切片总数没有达到总数时 |
| | | loadNext() |
| | | } else { |
| | | this.shardupload(params) |
| | | } |
| | | } |
| | | |
| | | chunkFileReader.onerror = () => { |
| | | this.onFail('文件读取失败!') |
| | | } |
| | | totalFileReader.onerror = () => { |
| | | this.onFail('文件读取失败!') |
| | | } |
| | | |
| | | let loadNext = () => { |
| | | let start = currentChunk * chunkSize // 计算分片的起始位置 |
| | | let end = Math.min(file.size, start + chunkSize) // 计算分片的结束位置 |
| | | |
| | | let obj = { // 每一个分片需要包含的信息 |
| | | chunk: currentChunk + 1, |
| | | binary: file.slice(start, end), |
| | | start: start, |
| | | end: end, |
| | | chunks |
| | | } |
| | | |
| | | params.chunks.push(obj) |
| | | chunkFileReader.readAsArrayBuffer(blobSlice.call(file, start, end)) |
| | | } |
| | | |
| | | return false |
| | |
| | | width: 90px; |
| | | height: 90px; |
| | | } |
| | | .ant-upload-list-picture-card .ant-upload-list-item-undefined { |
| | | border-color: #f5222d; |
| | | .ant-upload-list-item-name { |
| | | color: #f5222d; |
| | | } |
| | | } |
| | | // .ant-upload-list-picture-card .ant-upload-list-item-undefined { |
| | | // border-color: #f5222d; |
| | | // .ant-upload-list-item-name { |
| | | // color: #f5222d; |
| | | // } |
| | | // } |
| | | .ant-upload-list-picture-card .ant-upload-list-item { |
| | | width: 90px; |
| | | height: 90px; |
| | |
| | | precision: 0, |
| | | label: '比例', |
| | | initVal: card.ratio || 85, |
| | | tooltip: '小于100为宽度(或高度)百分比,大于100为像素值。', |
| | | tooltip: '模态框或抽屉的宽度,小于100为窗口宽度(或高度)百分比,大于100为像素值。', |
| | | required: true |
| | | }, |
| | | { |
| | |
| | | } |
| | | |
| | | /** |
| | | * @description 获取下级模块 |
| | | * @return {String} selfId 当前组件id |
| | | */ |
| | | static getAnchors (components, selfId) { |
| | | let modules = components.map(item => { |
| | | if (item.uuid === selfId) { |
| | | return { |
| | | children: null |
| | | } |
| | | } else if (item.type === 'tabs') { |
| | | let _item = { |
| | | type: 'tabs', |
| | | value: item.uuid, |
| | | label: item.name, |
| | | children: item.subtabs.map(f_tab => { |
| | | let subItem = { |
| | | type: 'tab', |
| | | value: f_tab.uuid, |
| | | label: f_tab.label, |
| | | children: this.getSubModules(f_tab.components, selfId) |
| | | } |
| | | |
| | | if (!subItem.children || subItem.children.length === 0) { |
| | | return {children: null} |
| | | } |
| | | return subItem |
| | | }) |
| | | } |
| | | |
| | | _item.children = _item.children.filter(t => t.children !== null) |
| | | |
| | | if (_item.children.length === 0) { |
| | | return {children: null} |
| | | } |
| | | |
| | | return _item |
| | | } else if (item.type === 'group') { |
| | | let _item = { |
| | | value: item.uuid, |
| | | label: item.name, |
| | | children: item.components.map(f_tab => { |
| | | if (f_tab.uuid === selfId) { |
| | | return { |
| | | children: null |
| | | } |
| | | } else if (f_tab.format) { |
| | | return { |
| | | value: f_tab.uuid, |
| | | label: f_tab.name |
| | | } |
| | | } |
| | | return { |
| | | children: null |
| | | } |
| | | }) |
| | | } |
| | | |
| | | _item.children = _item.children.filter(t => t.children !== null) |
| | | |
| | | if (_item.children.length === 0) { |
| | | return {children: null} |
| | | } |
| | | |
| | | return _item |
| | | } else if (!['login', 'navbar', 'topbar', 'tabs', 'search', 'group', 'balcony'].includes(item.type)) { // 数据格式,存在数据源 |
| | | return { |
| | | value: item.uuid, |
| | | label: item.name |
| | | } |
| | | } else { |
| | | return { |
| | | children: null |
| | | } |
| | | } |
| | | }) |
| | | |
| | | modules = modules.filter(mod => mod.children !== null) |
| | | |
| | | if (modules.length === 0) { |
| | | return null |
| | | } |
| | | return modules |
| | | } |
| | | |
| | | /** |
| | | * @description 获取上级模块 |
| | | * @return {String} selfId 当前组件id |
| | | */ |