update 去掉无用组件,修改控制台报错

This commit is contained in:
赵霄
2023-09-11 10:02:40 +08:00
parent 3cd0745366
commit c9aabf2f3d
58 changed files with 2 additions and 7648 deletions
+1 -4
View File
@@ -47,10 +47,7 @@
"vue-router": "^3.0.1",
"vue-splitpane": "^1.0.4",
"vuedraggable": "^2.20.0",
"vuex": "^3.1.0",
"vxe-table": "^3.6.10",
"vxe-table-plugin-antd": "^1.11.2",
"xe-utils": "^3.5.7"
"vuex": "^3.1.0"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",
-232
View File
@@ -1,232 +0,0 @@
<template>
<div>
<template v-if="hasFile">
<template v-for="(file, fileKey) of [innerFile || {}]">
<div :key="fileKey" style="position: relative;">
<a-tooltip v-if="file.status==='uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading"/>
<span style="margin-left:5px">上传中</span>
</a-tooltip>
<a-tooltip v-else-if="file.status==='done'" :title="file.name">
<a-icon type="paper-clip"/>
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
</a-tooltip>
<a-tooltip v-else :title="file.message||'上传失败'">
<a-icon type="exclamation-circle" style="color:red;"/>
<span style="margin-left:5px">{{ ellipsisFileName }}</span>
</a-tooltip>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
<a-tooltip title="操作">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '上传文件'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
<script>
import { getFileAccessHttpUrl } from '@api/manage'
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import JFilePop from '@/components/jero/minipop/JFilePop'
import JVxeUploadCell from '@/components/jero/JVxeTable/components/cells/JVxeUploadCell'
export default {
name: 'JVxeFileCell',
mixins: [JVxeCellMixins],
components: { JFilePop },
props: {},
data () {
return {
innerFile: null,
number: 0
}
},
computed: {
/** upload headers */
uploadHeaders () {
const { originColumn: col } = this
const headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
/** 上传请求地址 */
uploadAction () {
if (!this.originColumn.action) {
return window._CONFIG.domianURL + '/sys/common/upload'
} else {
return this.originColumn.action
}
},
hasFile () {
return this.innerFile != null
},
ellipsisFileName () {
const length = 5
const file = this.innerFile
if (!file || !file.name) {
return ''
}
if (file.name.length > length) {
return file.name.substr(0, length) + '…'
}
return file.name
},
responseName () {
if (this.originColumn.responseName) {
return this.originColumn.responseName
} else {
return 'message'
}
}
},
watch: {
innerValue: {
immediate: true,
handler () {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
}
}
},
methods: {
// 点击更多按钮
handleMoreOperation (originColumn) {
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
if (originColumn.number) {
this.number = originColumn.number
} else {
this.number = 0
}
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
if (originColumn && originColumn.fieldExtendJson) {
const json = JSON.parse(originColumn.fieldExtendJson)
this.number = json.uploadnum ? json.uploadnum : 0
}
let path = ''
if (this.innerFile) {
path = this.innerFile.path
}
this.$refs.filePop.show('', path)
},
// 更多上传回调
handleFileSuccess (file) {
if (file) {
this.innerFile.path = file.path
this.handleChangeCommon(this.innerFile)
}
},
handleChangeUpload (info) {
// const { originColumn: col } = this
const { file } = info
const value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (file.response) {
value.responseName = file.response[this.responseName]
}
if (file.status === 'done') {
if (typeof file.response.success === 'boolean') {
if (file.response.success) {
value.path = file.response[this.responseName]
this.handleChangeCommon(value)
} else {
value.status = 'error'
value.message = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
value.path = file.response[this.responseName]
this.handleChangeCommon(value)
}
} else if (file.status === 'error') {
value.message = file.response.message || '未知错误'
}
this.innerFile = value
},
handleClickDownloadFile () {
let { url, path } = this.innerFile || {}
if (!url || url.length === 0) {
if (path && path.length > 0) {
url = getFileAccessHttpUrl(path.split(',')[0])
}
}
if (url) {
window.open(url)
}
},
handleClickDeleteFile () {
this.handleChangeCommon(null)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: { visible: true },
getValue: value => JVxeUploadCell.enhanced.getValue(value),
setValue: value => JVxeUploadCell.enhanced.setValue(value)
}
}
</script>
<style scoped lang="less">
</style>
-245
View File
@@ -1,245 +0,0 @@
<template>
<div>
<template v-if="hasFile">
<template v-for="(file, fileKey) of [innerFile || {}]">
<div :key="fileKey" style="position: relative;">
<template v-if="!file || !(file['url'] || file['path'] || file['message'])">
<a-tooltip :title="'请稍后: ' + JSON.stringify (file) + ((file['url'] || file['path'] || file['message']))">
<a-icon type="loading"/>
</a-tooltip>
</template>
<template v-else-if="file['path']">
<img class="j-editable-image" :src="imgSrc" alt="无图片" @click="handleMoreOperation"/>
</template>
<a-tooltip v-else :title="file.message||'上传失败'" @click="handleClickShowImageError">
<a-icon type="exclamation-circle" style="color:red;"/>
</a-tooltip>
<template style="width: 30px">
<a-dropdown :trigger="['click']" placement="bottomRight" style="margin-left: 10px;">
<a-tooltip title="操作">
<a-icon
v-if="file.status!=='uploading'"
type="setting"
style="cursor: pointer;"/>
</a-tooltip>
<a-menu slot="overlay">
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download"/>&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete"/>&nbsp;删除</span>
</a-menu-item>
<a-menu-item @click="handleMoreOperation(originColumn)">
<span><a-icon type="bars"/> 更多</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</div>
</template>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload"
>
<a-button icon="upload">{{originColumn.btnText || '上传图片'}}</a-button>
</a-upload>
<j-file-pop ref="filePop" @ok="handleFileSuccess" :number="number"/>
</div>
</template>
<script>
import { getFileAccessHttpUrl } from '@api/manage'
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import JFilePop from '@/components/jero/minipop/JFilePop'
import JVxeUploadCell from '@/components/jero/JVxeTable/components/cells/JVxeUploadCell'
export default {
name: 'JVxeImageCell',
mixins: [JVxeCellMixins],
components: { JFilePop },
props: {},
data () {
return {
innerFile: null,
number: 0
}
},
computed: {
/** upload headers */
uploadHeaders () {
const { originColumn: col } = this
const headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
/** 上传请求地址 */
uploadAction () {
if (!this.originColumn.action) {
return window._CONFIG.domianURL + '/sys/common/upload'
} else {
return this.originColumn.action
}
},
hasFile () {
return this.innerFile != null
},
/** 预览图片地址 */
imgSrc () {
if (this.innerFile) {
if (this.innerFile.url) {
return this.innerFile.url
} else if (this.innerFile.path) {
const path = this.innerFile.path.split(',')[0]
return getFileAccessHttpUrl(path)
}
}
return ''
},
responseName () {
if (this.originColumn.responseName) {
return this.originColumn.responseName
} else {
return 'message'
}
}
},
watch: {
innerValue: {
immediate: true,
handler () {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
}
}
},
methods: {
// 点击更多按钮
handleMoreOperation (originColumn) {
// update-begin-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
if (originColumn.number) {
this.number = originColumn.number
} else {
this.number = 0
}
// update-end-author:wangshuai date:20201021 for:LOWCOD-969 判断传过来的字段是否存在number,用于控制上传文件
if (originColumn && originColumn.fieldExtendJson) {
const json = JSON.parse(originColumn.fieldExtendJson)
this.number = json.uploadnum ? json.uploadnum : 0
}
let path = ''
if (this.innerFile) {
path = this.innerFile.path
}
this.$refs.filePop.show('', path, 'img')
},
// 更多上传回调
handleFileSuccess (file) {
if (file) {
this.innerFile.path = file.path
this.handleChangeCommon(this.innerFile)
}
},
// 弹出上传出错详细信息
handleClickShowImageError () {
const file = this.innerFile || null
if (file && file.message) {
this.$error({ title: '上传出错', content: '错误信息:' + file.message, maskClosable: true })
}
},
handleChangeUpload (info) {
// const { originColumn: col } = this
const { file } = info
const value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (file.response) {
value.responseName = file.response[this.responseName]
}
if (file.status === 'done') {
if (typeof file.response.success === 'boolean') {
if (file.response.success) {
value.path = file.response[this.responseName]
this.handleChangeCommon(value)
} else {
value.status = 'error'
value.message = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
value.path = file.response[this.responseName]
this.handleChangeCommon(value)
}
} else if (file.status === 'error') {
value.message = file.response.message || '未知错误'
}
this.innerFile = value
},
handleClickDownloadFile () {
if (this.imgSrc) {
window.open(this.imgSrc)
}
},
handleClickDeleteFile () {
this.handleChangeCommon(null)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: { visible: true },
getValue: value => JVxeUploadCell.enhanced.getValue(value),
setValue: value => JVxeUploadCell.enhanced.setValue(value)
}
}
</script>
<style scoped lang="less">
.j-editable-image {
height: 32px;
max-width: 100px !important;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
</style>
@@ -1,63 +0,0 @@
<template>
<j-popup
v-bind="popupProps"
@input="handlePopupInput"
/>
</template>
<script>
import JVxeCellMixins, { dispatchEvent, vModel } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxePopupCell',
mixins: [JVxeCellMixins],
computed: {
popupProps () {
const { innerValue, originColumn: col, caseId, cellProps } = this
return {
...cellProps,
value: innerValue,
field: col.field || col.key,
code: col.popupCode,
orgFields: col.orgFields,
destFields: col.destFields,
groupId: caseId,
param: col.param,
sorter: col.sorter
}
}
},
methods: {
/** popup回调 */
handlePopupInput (value, others) {
const { row, originColumn: col } = this
// 存储输入的值
let popupValue = value
if (others && Object.keys(others).length > 0) {
Object.keys(others).forEach(key => {
const currentValue = others[key]
// 当前列直接赋值,其他列通过vModel赋值
if (key === col.key) {
popupValue = currentValue
} else {
vModel.call(this, currentValue, row, key)
}
})
}
this.handleChangeCommon(popupValue)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived (event) {
dispatchEvent.call(this, event, 'ant-input')
}
}
}
}
</script>
<style scoped>
</style>
@@ -1,60 +0,0 @@
<template>
<a-radio-group
:class="clazz"
:value="innerValue"
v-bind="cellProps"
@change="(e)=>handleChangeCommon(e.target.value)"
>
<a-radio
v-for="item of originColumn.options"
:key="item.value"
:value="item.value"
@click="$event=>handleRadioClick(item,$event)"
>{{ item.text }}
</a-radio>
</a-radio-group>
</template>
<script>
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeRadioCell',
mixins: [JVxeCellMixins],
computed: {
scrolling () {
return !!this.renderOptions.scrolling
},
clazz () {
return {
'j-vxe-radio': true,
'no-animation': this.scrolling
}
}
},
methods: {
handleRadioClick (item) {
if (this.originColumn.allowClear === true) {
// 取消选择
if (item.value === this.innerValue) {
this.handleChangeCommon(null)
}
}
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: { visible: true }
}
}
</script>
<style lang="less">
// 关闭动画,防止滚动时动态赋值出现问题
.j-vxe-radio.no-animation {
.ant-radio-inner,
.ant-radio-inner::after {
transition: none !important;
}
}
</style>
@@ -1,262 +0,0 @@
import debounce from 'lodash/debounce'
import { getAction } from '@/api/manage'
import { cloneObject } from '@/utils/util'
import { filterDictText } from '@/components/dict/JDictSelectUtil'
import { ajaxGetDictItems, getDictItemsFromCache } from '@/api/api'
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
/** 公共资源 */
const common = {
/** value - label map,防止重复查询(刷新清空缓存) */
labelMap: new Map(),
/** 公共data */
data () {
return {
loading: false,
innerSelectValue: null,
innerOptions: []
}
},
/** 公共计算属性 */
computed: {
dict () {
return this.originColumn.dict
},
options () {
if (this.isAsync) {
return this.innerOptions
} else {
return this.originColumn.options || []
}
},
// 是否是异步模式
isAsync () {
const isAsync = this.originColumn.async
return (isAsync != null && isAsync !== '') ? !!isAsync : true
}
},
/** 公共属性监听 */
watch: {
innerValue: {
immediate: true,
handler (value) {
if (value == null || value === '') {
this.innerSelectValue = null
} else {
this.loadDataByValue(value)
}
}
},
dict () {
this.loadDataByDict()
}
},
/** 公共方法 */
methods: {
// 根据 value 查询数据,用于回显
async loadDataByValue (value) {
if (this.isAsync) {
if (this.innerSelectValue !== value) {
if (common.labelMap.has(value)) {
this.innerOptions = cloneObject(common.labelMap.get(value))
} else {
const { success, result } = await getAction(`/sys/dict/loadDictItem/${this.dict}`, { key: value })
if (success && result && result.length > 0) {
this.innerOptions = [{ value: value, text: result[0] }]
common.labelMap.set(value, cloneObject(this.innerOptions))
}
}
}
}
this.innerSelectValue = (value || '').toString()
},
// 初始化字典
async loadDataByDict () {
if (!this.isAsync) {
// 如果字典项集合有数据
if (!this.originColumn.options || this.originColumn.options.length === 0) {
// 根据字典Code, 初始化字典数组
let dictStr = ''
if (this.dict) {
const arr = this.dict.split(',')
if (arr[0].indexOf('where') > 0) {
const tbInfo = arr[0].split('where')
dictStr = tbInfo[0].trim() + ',' + arr[1] + ',' + arr[2] + ',' + encodeURIComponent(tbInfo[1])
} else {
dictStr = this.dict
}
if (this.dict.indexOf(',') === -1) {
// 优先从缓存中读取字典配置
const cache = getDictItemsFromCache(this.dict)
if (cache) {
this.innerOptions = cache
return
}
}
const { success, result } = await ajaxGetDictItems(dictStr, null)
if (success) {
this.innerOptions = result
}
}
}
}
}
}
}
// 显示组件,自带翻译
export const DictSearchSpanCell = {
name: 'JVxeSelectSearchSpanCell',
mixins: [JVxeCellMixins],
data () {
return {
...common.data.apply(this)
}
},
computed: {
...common.computed
},
watch: {
...common.watch
},
methods: {
...common.methods
},
render (h) {
return h('span', {}, [
filterDictText(this.innerOptions, this.innerSelectValue || this.innerValue)
])
}
}
// 请求id
let requestId = 0
// 输入选择组件
export const DictSearchInputCell = {
name: 'JVxeSelectSearchInputCell',
mixins: [JVxeCellMixins],
data () {
return {
...common.data.apply(this),
hasRequest: false,
scopedSlots: {
notFoundContent: () => {
if (this.loading) {
return <a-spin size="small"/>
} else if (this.hasRequest) {
return <div>没有查询到任何数据</div>
} else {
return <div>{this.tipsContent}</div>
}
}
}
}
},
computed: {
...common.computed,
tipsContent () {
return this.originColumn.tipsContent || '请输入搜索内容'
},
filterOption () {
if (this.isAsync) {
return null
}
return (input, option) => option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
},
watch: {
...common.watch
},
created () {
this.loadData = debounce(this.loadData, 300)// 消抖
},
methods: {
...common.methods,
loadData (value) {
const currentRequestId = ++requestId
this.loading = true
this.innerOptions = []
if (value == null || value.trim() === '') {
this.loading = false
this.hasRequest = false
return
}
// 字典code格式:table,text,code
this.hasRequest = true
getAction(`/sys/dict/loadDict/${this.dict}`, { keyword: value }).then(res => {
if (currentRequestId !== requestId) {
return
}
const { success, result, message } = res
if (success) {
this.innerOptions = result
result.forEach((item) => {
common.labelMap.set(item.value, [item])
})
} else {
this.$message.warning(message)
}
}).finally(() => {
this.loading = false
})
},
handleChange (selectedValue) {
this.innerSelectValue = selectedValue
this.handleChangeCommon(this.innerSelectValue)
},
handleSearch (value) {
if (this.isAsync) {
// 在输入时也应该开启加载,因为loadData加了消抖,所以会有800ms的用户主观上认为的卡顿时间
this.loading = true
if (this.innerOptions.length > 0) {
this.innerOptions = []
}
this.loadData(value)
}
},
renderOptionItem () {
const options = []
this.options.forEach(({ value, text, label, title, disabled }) => {
options.push(
<a-select-option key={value} value={value} disabled={disabled}>{text || label || title}</a-select-option>
)
})
return options
}
},
render () {
return (
<a-select
showSearch
allowClear
value={this.innerSelectValue}
filterOption={this.filterOption}
style="width: 100%"
{...this.cellProps}
onSearch={this.handleSearch}
onChange={this.handleChange}
scopedSlots={this.scopedSlots}
>
{this.renderOptionItem()}
</a-select>
)
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived (event) {
dispatchEvent.call(this, event, 'ant-select')
}
}
}
}
-36
View File
@@ -1,36 +0,0 @@
import { installCell, JVXETypes } from '@/components/jero/JVxeTable'
import JVxePopupCell from './JVxePopupCell'
import { DictSearchInputCell, DictSearchSpanCell } from './JVxeSelectDictSearchCell'
import JVxeFileCell from './JVxeFileCell'
import JVxeImageCell from './JVxeImageCell'
import JVxeRadioCell from './JVxeRadioCell'
import JVxeSelectCell from '@comp/jero/JVxeTable/components/cells/JVxeSelectCell'
import JVxeTextareaCell from '@comp/jero/JVxeTable/components/cells/JVxeTextareaCell'
// 注册online组件
JVXETypes.input_pop = 'input_pop'
JVXETypes.list_multi = 'list_multi'
JVXETypes.sel_search = 'sel_search'
installCell(JVXETypes.input_pop, JVxeTextareaCell)
installCell(JVXETypes.list_multi, JVxeSelectCell)
installCell(JVXETypes.sel_search, JVxeSelectCell)
// 注册【popup】组件(普通封装方式)
JVXETypes.popup = 'popup'
installCell(JVXETypes.popup, JVxePopupCell)
// 注册【字典搜索下拉】组件(高级封装方式)
JVXETypes.selectDictSearch = 'select-dict-search'
installCell(JVXETypes.selectDictSearch, DictSearchInputCell, DictSearchSpanCell)
// 注册【文件上传】组件
JVXETypes.file = 'file'
installCell(JVXETypes.file, JVxeFileCell)
// 注册【图片上传】组件
JVXETypes.image = 'image'
installCell(JVXETypes.image, JVxeImageCell)
// 注册【单选框】组件
JVXETypes.radio = 'radio'
installCell(JVXETypes.radio, JVxeRadioCell)
-88
View File
@@ -1,88 +0,0 @@
<template>
<div :style="{ padding: '0' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart ref="chart" :forceFit="true" :height="height" :data="dataSource" :scale="scale">
<v-tooltip :shared="false"/>
<v-axis/>
<v-line position="x*y" :size="lineSize" :color="lineColor"/>
<v-area position="x*y" :color="color"/>
</v-chart>
</div>
</template>
<script>
import { triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'AreaChartTy',
props: {
// 图表数据
dataSource: {
type: Array,
required: true
},
// 图表标题
title: {
type: String,
default: ''
},
// x 轴别名
x: {
type: String,
default: 'x'
},
// y 轴别名
y: {
type: String,
default: 'y'
},
// Y轴最小值
min: {
type: Number,
default: 0
},
// Y轴最大值
max: {
type: Number,
default: null
},
// 图表高度
height: {
type: Number,
default: 254
},
// 线的粗细
lineSize: {
type: Number,
default: 2
},
// 面积的颜色
color: {
type: String,
default: ''
},
// 线的颜色
lineColor: {
type: String,
default: ''
}
},
computed: {
scale () {
return [
{ dataKey: 'x', title: this.x, alias: this.x },
{ dataKey: 'y', title: this.y, alias: this.y, min: this.min, max: this.max }
]
}
},
mounted () {
triggerWindowResizeEvent()
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>
-50
View File
@@ -1,50 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="height" :data="dataSource" :scale="scale" :padding="padding">
<v-tooltip/>
<v-axis/>
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
import { triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'Bar',
props: {
dataSource: {
type: Array,
required: true
},
yaxisText: {
type: String,
default: 'y'
},
title: {
type: String,
default: ''
},
height: {
type: Number,
default: 254
}
},
data () {
return { padding: ['auto', 'auto', '40', '50'] }
},
computed: {
scale () {
return [{
dataKey: 'y',
alias: this.yaxisText
}]
}
},
mounted () {
triggerWindowResizeEvent()
}
}
</script>
-60
View File
@@ -1,60 +0,0 @@
<template>
<div :style="{ padding: '0 50px 32px 0' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding" :onClick="handleClick">
<v-tooltip/>
<v-legend/>
<v-axis/>
<v-bar position="type*bar"/>
<v-line position="type*line" color="#2fc25b" :size="3"/>
</v-chart>
</div>
</template>
<script>
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'BarAndLine',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource: {
type: Array,
default: () => [
{ type: '10:10', bar: 200, line: 1000 },
{ type: '10:15', bar: 600, line: 1000 },
{ type: '10:20', bar: 200, line: 1000 },
{ type: '10:25', bar: 900, line: 1000 },
{ type: '10:30', bar: 200, line: 1000 },
{ type: '10:35', bar: 200, line: 1000 },
{ type: '10:40', bar: 100, line: 1000 }
]
},
height: {
type: Number,
default: 400
}
},
data () {
return {
padding: { top: 50, right: 50, bottom: 100, left: 50 },
scale: [{
dataKey: 'bar',
min: 0
}, {
dataKey: 'line',
min: 0
}]
}
},
computed: {
data () {
return this.dataSource
}
}
}
</script>
-96
View File
@@ -1,96 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :data="data" :height="height" :force-fit="true" :scale="scale" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
<v-bar position="x*y" color="type" :adjust="adjust"/>
</v-chart>
</div>
</template>
<script>
import { DataSet } from '@antv/data-set'
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'BarMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource: {
type: Array,
default: () => [
{ type: 'JeroOne', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, May: 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
{ type: 'JeroTwo', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, May: 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 }
]
},
fields: {
type: Array,
default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
},
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
aliases: {
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data () {
return {
adjust: [{
type: 'dodge',
marginRatio: 1 / 32
}]
}
},
computed: {
data () {
const dv = new DataSet.View().source(this.dataSource)
dv.transform({
type: 'fold',
fields: this.fields,
key: 'x',
value: 'y'
})
// bar 使用不了 - 和 / 所以替换下
const rows = dv.rows.map(row => {
if (typeof row.x === 'string') {
row.x = row.x.replace(/[-/]/g, '_')
}
return row
})
// 替换别名
rows.forEach(row => {
for (const item of this.aliases) {
if (item.field === row.type) {
row.type = item.alias
break
}
}
})
return rows
},
scale () {
return [
{
type: 'cat',
dataKey: 'x'
}
]
}
}
}
</script>
<style scoped>
</style>
-187
View File
@@ -1,187 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<v-chart :forceFit="true" :height="300" :data="chartData" :scale="scale">
<v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
<v-axis
dataKey="value"
:zIndex="2"
:line="null"
:label="axisLabel"
:subTickCount="4"
:subTickLine="axisSubTickLine"
:tickLine="axisTickLine"
:grid="null"
></v-axis>
<v-axis dataKey="1" :show="false"></v-axis>
<v-series
gemo="point"
position="value*1"
shape="pointer"
color="#1890FF"
:active="false"
></v-series>
<v-guide
type="arc"
:zIndex="0"
:top="false"
:start="arcGuide1Start"
:end="arcGuide1End"
:vStyle="arcGuide1Style"
></v-guide>
<v-guide
type="arc"
:zIndex="1"
:start="arcGuide2Start"
:end="getArcGuide2End"
:vStyle="arcGuide2Style"
></v-guide>
<v-guide
type="html"
:position="htmlGuidePosition"
:html="getHtmlGuideHtml()"
></v-guide>
</v-chart>
</div>
</template>
<script>
import { registerShape } from 'viser-vue'
registerShape('point', 'pointer', {
draw (cfg, container) {
let point = cfg.points[0]
point = this.parsePoint(point)
const center = this.parsePoint({
x: 0,
y: 0
})
container.addShape('line', {
attrs: {
x1: center.x,
y1: center.y,
x2: point.x,
y2: point.y + 15,
stroke: cfg.color,
lineWidth: 5,
lineCap: 'round'
}
})
return container.addShape('circle', {
attrs: {
x: center.x,
y: center.y,
r: 9.75,
stroke: cfg.color,
lineWidth: 4.5,
fill: '#fff'
}
})
}
})
const scale = [{
dataKey: 'value',
min: 0,
max: 9,
tickInterval: 1,
nice: false
}]
const data = [
{ value: 7.0 }
]
export default {
name: 'DashChartDemo',
props: {
datasource: {
type: Number,
default: 7
},
title: {
type: String,
default: ''
}
},
created () {
if (!this.datasource) {
this.chartData = data
} else {
this.chartData = [
{ value: this.datasource }
]
}
this.getChartData()
},
watch: {
datasource: function (val) {
this.chartData = [
{ value: val }
]
this.getChartData()
}
},
methods: {
getChartData () {
if (this.chartData && this.chartData.length > 0) {
this.abcd = this.chartData[0].value * 10
} else {
this.abcd = 70
}
},
getHtmlGuideHtml () {
return '<div style="width: 300px;text-align: center;">\n' +
'<p style="font-size: 14px;color: #545454;margin: 0;">' + this.title + '</p>\n' +
'<p style="font-size: 36px;color: #545454;margin: 0;">' + this.abcd + '%</p>\n' +
'</div>'
},
getArcGuide2End () {
return [this.chartData[0].value, 0.945]
}
},
data () {
return {
chartData: [],
height: 400,
scale: scale,
abcd: 70,
axisLabel: {
offset: -16,
textStyle: {
fontSize: 18,
textAlign: 'center',
textBaseline: 'middle'
}
},
axisSubTickLine: {
length: -8,
stroke: '#fff',
strokeOpacity: 1
},
axisTickLine: {
length: -17,
stroke: '#fff',
strokeOpacity: 1
},
arcGuide1Start: [0, 0.945],
arcGuide1End: [9, 0.945],
arcGuide1Style: {
stroke: '#CBCBCB',
lineWidth: 18
},
arcGuide2Start: [0, 0.945],
arcGuide2Style: {
stroke: '#1890FF',
lineWidth: 18
},
htmlGuidePosition: ['50%', '100%'],
htmlGuideHtml: `
<div style="width: 300px;text-align: center;">
<p style="font-size: 14px;color: #545454;margin: 0;">${this.title}</p>
<p style="font-size: 36px;color: #545454;margin: 0;">${this.abcd}%</p>
</div>
`
}
}
}
</script>
-61
View File
@@ -1,61 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="datasource"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
const data = []
for (let i = 0; i < 12; i += 1) {
data.push({
x: `${i + 1}`,
y: Math.floor(Math.random() * 1000) + 200
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
}
},
mounted () {
this.datasource = data
},
data () {
return {
datasource: [],
scale,
tooltip
}
}
}
</script>
-95
View File
@@ -1,95 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
<v-line position="type*y" color="x"/>
<v-point position="type*y" color="x" :size="4" :v-style="style" :shape="'circle'"/>
</v-chart>
</div>
</template>
<script>
import { DataSet } from '@antv/data-set'
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'LineChartMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource: {
type: Array,
default: () => [
{ type: 'Jan', jeroOne: 7.0, jeroTwo: 3.9 },
{ type: 'Feb', jeroOne: 6.9, jeroTwo: 4.2 },
{ type: 'Mar', jeroOne: 9.5, jeroTwo: 5.7 },
{ type: 'Apr', jeroOne: 14.5, jeroTwo: 8.5 },
{ type: 'May', jeroOne: 18.4, jeroTwo: 11.9 },
{ type: 'Jun', jeroOne: 21.5, jeroTwo: 15.2 },
{ type: 'Jul', jeroOne: 25.2, jeroTwo: 17.0 },
{ type: 'Aug', jeroOne: 26.5, jeroTwo: 16.6 },
{ type: 'Sep', jeroOne: 23.3, jeroTwo: 14.2 },
{ type: 'Oct', jeroOne: 18.3, jeroTwo: 10.3 },
{ type: 'Nov', jeroOne: 13.9, jeroTwo: 6.6 },
{ type: 'Dec', jeroOne: 9.6, jeroTwo: 4.8 }
]
},
fields: {
type: Array,
default: () => ['jeroOne', 'jeroTwo']
},
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
aliases: {
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data () {
return {
scale: [{
type: 'cat',
dataKey: 'x',
min: 0,
max: 1
}],
style: { stroke: '#fff', lineWidth: 1 }
}
},
computed: {
data () {
const dv = new DataSet.View().source(this.dataSource)
dv.transform({
type: 'fold',
fields: this.fields,
key: 'x',
value: 'y'
})
const rows = dv.rows
// 替换别名
rows.forEach(row => {
for (const item of this.aliases) {
if (item.field === row.x) {
row.x = item.alias
break
}
}
})
return rows
}
}
}
</script>
<style scoped>
</style>
-80
View File
@@ -1,80 +0,0 @@
<template>
<div>
<v-chart
:forceFit="true"
:height="height"
:width="width"
:data="data"
:scale="scale"
:padding="0">
<v-tooltip/>
<v-interval
:shape="['liquid-fill-gauge']"
position="transfer*value"
color=""
:v-style="{
lineWidth: 8,
opacity: 0.75
}"
:tooltip="[
'transfer*value',
(transfer, value) => {
return {
name: transfer,
value
};
},
]"
></v-interval>
<v-guide
v-for="(row, index) in data"
:key="index"
type="text"
:top="true"
:position="{
gender: row.transfer,
value: 45
}"
:content="row.value + '%'"
:v-style="{
fontSize: 100,
textAlign: 'center',
opacity: 0.75,
}"
/>
</v-chart>
</div>
</template>
<script>
const sourceDataConst = [
{ transfer: '一月', value: 813 },
{ transfer: '二月', value: 233 },
{ transfer: '三月', value: 561 }
]
export default {
name: 'Liquid',
props: {
height: {
type: Number,
default: 0
},
width: {
type: Number,
default: 0
}
},
data () {
return {
data: sourceDataConst,
scale: []
}
}
}
</script>
<style scoped>
</style>
-69
View File
@@ -1,69 +0,0 @@
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :padding="[36, 0, 18, 0]">
<v-tooltip/>
<v-smooth-area position="x*y"/>
</v-chart>
</div>
</div>
</template>
<script>
import moment from 'dayjs'
const sourceData = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
sourceData.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
export default {
name: 'MiniArea',
props: {
dataSource: {
type: Array,
default: () => []
},
// x 轴别名
x: {
type: String,
default: 'x'
},
// y 轴别名
y: {
type: String,
default: 'y'
}
},
data () {
return {
data: [],
height: 100
}
},
computed: {
scale () {
return [
{ dataKey: 'x', title: this.x, alias: this.x },
{ dataKey: 'y', title: this.y, alias: this.y }
]
}
},
created () {
if (this.dataSource.length === 0) {
this.data = sourceData
} else {
this.data = this.dataSource
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>
-76
View File
@@ -1,76 +0,0 @@
<template>
<div :style="{'width':width===null?'auto':width+'px'}">
<v-chart :forceFit="typeof width !== 'number'" :height="height" :data="data" padding="0">
<v-tooltip/>
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
import moment from 'dayjs'
const sourceData = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
sourceData.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 30
}]
export default {
name: 'MiniBar',
props: {
dataSource: {
type: Array,
default: () => []
},
width: {
type: Number,
default: null
},
height: {
type: Number,
default: 200
}
},
created () {
if (this.dataSource.length === 0) {
this.data = sourceData
} else {
this.data = this.dataSource
}
},
data () {
return {
tooltip,
data: [],
scale
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>
-75
View File
@@ -1,75 +0,0 @@
<template>
<div class="chart-mini-progress">
<div class="target" :style="{ left: target + '%'}">
<span :style="{ backgroundColor: color }"/>
<span :style="{ backgroundColor: color }"/>
</div>
<div class="progress-wrapper">
<div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height+'px' }"></div>
</div>
</div>
</template>
<script>
export default {
name: 'MiniProgress',
props: {
target: {
type: Number,
default: 0
},
height: {
type: Number,
default: 10
},
color: {
type: String,
default: '#13C2C2'
},
percentage: {
type: Number,
default: 0
}
}
}
</script>
<style lang="less" scoped>
.chart-mini-progress {
padding: 5px 0;
position: relative;
width: 100%;
.target {
position: absolute;
top: 0;
bottom: 0;
span {
border-radius: 100px;
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 2px;
&:last-child {
top: auto;
bottom: 0;
}
}
}
.progress-wrapper {
background-color: #f5f5f5;
position: relative;
.progress {
transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
border-radius: 1px 0 0 1px;
background-color: #1890ff;
width: 0;
height: 100%;
}
}
}
</style>
-70
View File
@@ -1,70 +0,0 @@
<template>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
<v-tooltip :showTitle="false" dataKey="item*percent"/>
<v-axis/>
<v-legend dataKey="item"/>
<v-pie position="percent" color="item" :v-style="pieStyle" :label="labelConfig"/>
<v-coord type="theta"/>
</v-chart>
</template>
<script>
import { ChartEventMixins } from './mixins/ChartMixins'
const DataSet = require('@antv/data-set')
export default {
name: 'Pie',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
height: {
type: Number,
default: 254
},
dataSource: {
type: Array,
default: () => [
{ item: '示例一', count: 40 },
{ item: '示例二', count: 21 },
{ item: '示例三', count: 17 },
{ item: '示例四', count: 13 },
{ item: '示例五', count: 9 }
]
}
},
data () {
return {
scale: [{
dataKey: 'percent',
min: 0,
formatter: '.0%'
}],
pieStyle: {
stroke: '#fff',
lineWidth: 1
},
labelConfig: ['percent', {
formatter: (val, item) => {
return item.point.item + ': ' + val
}
}]
}
},
computed: {
data () {
const dv = new DataSet.View().source(this.dataSource)
// 计算数据百分比
dv.transform({
type: 'percent',
field: 'count',
dimension: 'item',
as: 'percent'
})
return dv.rows
}
}
}
</script>
-367
View File
@@ -1,367 +0,0 @@
# 报表组件文档
## 柱状图
##### 引用方式
```js
import Bar from '@/components/chart/Bar'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|------------|
| title | string | | 报表标题 |
| dataSource | array | ✔️ | 报表数据源 |
| height | number | | 报表高度,默认254 |
##### dataSource 示例
```json
[
{
"x": "1月",
"y": 320
},
{
"x": "2月",
"y": 457
},
{
"x": "3月",
"y": 182
}
]
```
##### 代码示例
```html
<template>
<bar title="柱状图" :dataSource="dataSource" :height="420"/>
</template>
<script>
import Bar from '@/components/chart/Bar'
export default {
name: 'ChartDemo',
components: {
Bar
},
data() {
return {
dataSource: [
{
"x": "1月",
"y": 320
},
{
"x": "2月",
"y": 457
},
{
"x": "3月",
"y": 182
}
]
}
}
}
</script>
<style></style>
```
## 多列柱状图
##### 引用方式
```js
import BarMultid from '@/components/chart/BarMultid'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|------------|
| title | string | | 报表标题 |
| fields | array | | 主列字段列表 |
| dataSource | array | | 报表数据源 |
| height | number | | 报表高度,默认254 |
##### fields 示例
```json
["Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.", "Jul.", "Aug."]
```
##### dataSource 示例
```json
[
{
"type": "jero", // 列名
"Jan.": 18.9,
"Feb.": 28.8,
"Mar.": 39.3,
"Apr.": 81.4,
"May": 47,
"Jun.": 20.3,
"Jul.": 24,
"Aug.": 35.6
},
{
"type": "jeroTwo",
"Jan.": 12.4,
"Feb.": 23.2,
"Mar.": 34.5,
"Apr.": 99.7,
"May": 52.6,
"Jun.": 35.5,
"Jul.": 37.4,
"Aug.": 42.4
}
]
```
## 迷你柱状图
不带标题和数据轴的柱状图
##### 引用方式
```js
import MiniBar from '@/components/chart/MiniBar'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|---------------|
| width | number | | 报表宽度度,默认自适应宽度 |
| height | number | | 报表高度,默认200 |
| dataSource | array | | 报表数据源 |
##### dataSource 示例
```json
[
{
"x": "1月",
"y": 320
},
{
"x": "2月",
"y": 457
},
{
"x": "3月",
"y": 182
}
]
```
## 面积图
##### 引用方式
```js
import AreaChartTy from '@/components/chart/AreaChartTy'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|------------|
| title | string | | 报表标题 |
| dataSource | array | ✔️ | 报表数据源 |
| height | number | | 报表高度,默认254 |
| lineSize | number | | 线的粗细,默认2 |
##### dataSource 示例
```json
[
{
"x": "1月",
"y": 320
},
{
"x": "2月",
"y": 457
},
{
"x": "3月",
"y": 182
}
]
```
## 多行折线图
##### 引用方式
```js
import LineChartMultid from '@/components/chart/LineChartMultid'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|------------|
| title | string | | 报表标题 |
| fields | array | | 主列字段列表 |
| dataSource | array | | 报表数据源 |
| height | number | | 报表高度,默认254 |
##### fields 示例
```json
["jero", "jeroTwo"]
```
##### dataSource 示例
```json
[
{
"type": "Jan", // 列名
"jero": 7,
"jeroTwo": 3.9
},
{ "type": "Feb", "jero": 6.9, "jeroTwo": 4.2 },
{ "type": "Mar", "jero": 9.5, "jeroTwo": 5.7 },
{ "type": "Apr", "jero": 14.5, "jeroTwo": 8.5 },
{ "type": "May", "jero": 18.4, "jeroTwo": 11.9 },
{ "type": "Jun", "jero": 21.5, "jeroTwo": 15.2 },
{ "type": "Jul", "jero": 25.2, "jeroTwo": 17 },
{ "type": "Aug", "jero": 26.5, "jeroTwo": 16.6 },
{ "type": "Sep", "jero": 23.3, "jeroTwo": 14.2 },
{ "type": "Oct", "jero": 18.3, "jeroTwo": 10.3 },
{ "type": "Nov", "jero": 13.9, "jeroTwo": 6.6 },
{ "type": "Dec", "jero": 9.6, "jeroTwo": 4.8 }
]
```
## 饼状图
##### 引用方式
```js
import Pie from '@/components/chart/Pie'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|------------|
| dataSource | array | | 报表数据源 |
| height | number | | 报表高度,默认254 |
##### dataSource 示例
```json
[
// 所有的 percent 相加等于 100
{ "item": "一月", "percent": 40 },
{ "item": "二月", "percent": 21 },
{ "item": "三月", "percent": 17 },
{ "item": "四月", "percent": 13 },
{ "item": "五月", "percent": 9 }
]
```
## 雷达图
##### 引用方式
```js
import Radar from '@/components/chart/Radar'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|------------|
| dataSource | array | | 报表数据源 |
| height | number | | 报表高度,默认254 |
##### dataSource 示例
```json
[
// score 最小值为 0,最大值为 100
{ "item": "一月", "score": 40 },
{ "item": "二月", "score": 20 },
{ "item": "三月", "score": 67 },
{ "item": "四月", "score": 43 },
{ "item": "五月", "score": 90 }
]
```
## 进度条
##### 引用方式
```js
import MiniProgress from '@/components/chart/MiniProgress'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|------------|--------|----|-------------------|
| percentage | number | | 当前进度百分比,默认0,最高100 |
| target | number | | 目标值,默认10 |
| height | number | | 进度条高度,默认10 |
| color | string | | 进度条颜色,默认 #13C2C2 |
## 仪表盘
##### 引用方式
```js
import DashChartDemo from '@/components/chart/DashChartDemo'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|--------|--------|----|----------------|
| title | string | | 报表标题 |
| value | number | | 当前值,默认6.7,最大为9 |
| height | number | | 报表高度,默认254 |
## 排名列表
##### 引用方式
```js
import RankList from '@/components/chart/RankList'
```
##### 参数列表
| 参数名 | 类型 | 必填 | 说明 |
|--------|--------|----|--------------|
| title | string | | 报表标题 |
| list | array | | 排名列表数据 |
| height | number | | 报表高度,默认自适应高度 |
##### list 示例
```json
[
{
"name": "北京朝阳 1 号店",
"total": 1981
},
{ "name": "北京朝阳 2 号店", "total": 1359 },
{ "name": "北京朝阳 3 号店", "total": 1354 },
{ "name": "北京朝阳 4 号店", "total": 263 },
{ "name": "北京朝阳 5 号店", "total": 446 },
{ "name": "北京朝阳 6 号店", "total": 796 }
]
```
-90
View File
@@ -1,90 +0,0 @@
<template>
<v-chart :forceFit="true" :height="height" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
<v-tooltip></v-tooltip>
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid"/>
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid"/>
<v-legend dataKey="user" marker="circle" :offset="30"/>
<v-coord type="polar" radius="0.8"/>
<v-line position="item*score" color="user" :size="2"/>
<v-point position="item*score" color="user" :size="4" shape="circle"/>
</v-chart>
</template>
<script>
const axis1Opts = {
dataKey: 'item',
line: null,
tickLine: null,
grid: {
lineStyle: {
lineDash: null
},
hideFirstLine: false
}
}
const axis2Opts = {
dataKey: 'score',
line: null,
tickLine: null,
grid: {
type: 'polygon',
lineStyle: {
lineDash: null
}
}
}
const scale = [
{
dataKey: 'score',
min: 0,
max: 100
}, {
dataKey: 'user',
alias: '类型'
}
]
const sourceData = [
{ item: '示例一', score: 40 },
{ item: '示例二', score: 20 },
{ item: '示例三', score: 67 },
{ item: '示例四', score: 43 },
{ item: '示例五', score: 90 }
]
export default {
name: 'Radar',
props: {
height: {
type: Number,
default: 254
},
dataSource: {
type: Array,
default: () => []
}
},
data () {
return {
axis1Opts,
axis2Opts,
scale,
data: sourceData
}
},
watch: {
dataSource (newVal) {
if (newVal.length === 0) {
this.data = sourceData
} else {
this.data = newVal
}
}
}
}
</script>
<style scoped>
</style>
-81
View File
@@ -1,81 +0,0 @@
<template>
<div class="rank">
<h4 class="title">{{ title }}</h4>
<ul class="list" :style="{height:height?`${height}px`:'auto',overflow:'auto'}">
<li :key="index" v-for="(item, index) in list">
<span :class="index < 3 ? 'active' : null">{{ index + 1 }}</span>
<span>{{ item.name }}</span>
<span>{{ item.total }}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'RankList',
// ['title', 'list']
props: {
title: {
type: String,
default: ''
},
list: {
type: Array,
default: null
},
height: {
type: Number,
default: null
}
}
}
</script>
<style lang="less" scoped>
.rank {
padding: 0 32px 32px 72px;
.list {
margin: 25px 0 0;
padding: 0;
list-style: none;
li {
margin-top: 16px;
span {
color: rgba(0, 0, 0, .65);
font-size: 14px;
line-height: 22px;
&:first-child {
background-color: #f5f5f5;
border-radius: 20px;
display: inline-block;
font-size: 12px;
font-weight: 600;
margin-right: 24px;
height: 20px;
line-height: 20px;
width: 20px;
text-align: center;
}
&.active {
background-color: #314659;
color: #fff;
}
&:last-child {
float: right;
}
}
}
}
}
.mobile .rank {
padding: 0 32px 32px 32px;
}
</style>
-54
View File
@@ -1,54 +0,0 @@
<template>
<div>
<v-chart :forceFit="true" :height="height" :data="data">
<v-coord type="rect" direction="LB" />
<v-tooltip />
<v-legend />
<v-axis dataKey="State" :label="label" />
<v-stack-bar position="State*流程数量" color="流程状态" />
</v-chart>
</div>
</template>
<script>
const DataSet = require('@antv/data-set')
export default {
name: 'StackBar',
props: {
dataSource: {
type: Array,
required: true,
default: () => [
{ State: '请假', 流转中: 25, 已归档: 18 },
{ State: '出差', 流转中: 30, 已归档: 20 },
{ State: '加班', 流转中: 38, 已归档: 42 },
{ State: '用车', 流转中: 51, 已归档: 67 }
]
},
height: {
type: Number,
default: 254
}
},
data () {
return {
label: { offset: 12 }
}
},
computed: {
data () {
const dv = new DataSet.View().source(this.dataSource)
dv.transform({
type: 'fold',
fields: ['流转中', '已归档'],
key: '流程状态',
value: '流程数量',
retains: ['State']
})
return dv.rows
}
}
}
</script>
-66
View File
@@ -1,66 +0,0 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
:height="height"
:data="data"
:scale="scale"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip/>
<v-axis/>
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
},
x: {
type: String,
default: 'x'
},
y: {
type: String,
default: 'y'
},
data: {
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data () {
return {}
},
computed: {
scale () {
return [
{ dataKey: 'x', title: this.x, alias: this.x },
{ dataKey: 'y', title: this.y, alias: this.y }
]
}
},
created () {
// this.getMonthBar()
},
methods: {
// getMonthBar() {
// this.$http.get('/analysis/month-bar')
// .then(res => {
// this.data = res.result
// })
// }
}
}
</script>
-84
View File
@@ -1,84 +0,0 @@
<template>
<div class="chart-trend">
{{ term }}
<span>{{ rate }}%</span>
<span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
</div>
</template>
<script>
export default {
name: 'Trend',
props: {
// 同title
term: {
type: String,
default: '',
required: true
},
// 百分比
percentage: {
type: Number,
default: null
},
type: {
type: Boolean,
default: null
},
target: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
},
fixed: {
type: Number,
default: 2
}
},
data () {
return {
trend: (this.type && 'up') || 'down',
rate: this.percentage
}
},
created () {
const type = this.type === null ? this.value >= this.target : this.type
this.trend = type ? 'up' : 'down'
this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
}
}
</script>
<style lang="less" scoped>
.chart-trend {
display: inline-block;
font-size: 14px;
line-height: 22px;
.trend-icon {
font-size: 12px;
&.up, &.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(.83);
}
}
&.up {
color: #f5222d;
}
&.down {
color: #52c41a;
top: -1px;
}
}
}
</style>
-13
View File
@@ -1,13 +0,0 @@
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
/* margin: 0 -5px;
overflow: hidden;*/
}
}
@@ -1,10 +0,0 @@
export const ChartEventMixins = {
methods: {
handleClick (event, chart) {
this.handleEvent('click', event, chart)
},
handleEvent (eventName, event, chart) {
this.$emit(eventName, event, chart)
}
}
}
@@ -1,75 +0,0 @@
<template>
<j-modal
title="详细信息"
:width="1200"
:visible="visible"
@ok="handleOk"
@cancel="close"
switch-fullscreen
:fullscreen.sync="fullscreen"
>
<transition name="fade">
<div v-if="visible">
<slot name="mainForm" :row="row" :column="column"/>
<slot name="subForm" :row="row" :column="column"/>
</div>
</transition>
</j-modal>
</template>
<script>
import { cloneObject } from '@/utils/util'
export default {
name: 'JVxeDetailsModal',
inject: ['superTrigger'],
data () {
return {
visible: false,
fullscreen: false,
row: null,
column: null
}
},
created () {
},
methods: {
open (event) {
const { row, column } = event
this.row = cloneObject(row)
this.column = column
this.visible = true
},
close () {
this.visible = false
},
handleOk () {
this.superTrigger('detailsConfirm', {
row: this.row,
column: this.column,
callback: (success) => {
this.visible = !success
}
})
}
}
}
</script>
<style lang="less">
.fade-enter-active,
.fade-leave-active {
opacity: 1;
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
@@ -1,67 +0,0 @@
<template>
<div :class="boxClass">
<a-pagination
:disabled="disabled"
v-bind="bindProps"
@change="handleChange"
@showSizeChange="handleShowSizeChange"
/>
</div>
</template>
<script>
import PropTypes from 'ant-design-vue/es/_util/vue-types'
export default {
name: 'JVxePagination',
props: {
size: String,
disabled: PropTypes.bool,
pagination: PropTypes.object.def({})
},
data () {
return {
defaultPagination: {
current: 1,
pageSize: 10,
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return range[0] + '-' + range[1] + ' 共 ' + total + ' 条'
},
showQuickJumper: true,
showSizeChanger: true,
total: 100
}
}
},
computed: {
bindProps () {
return {
...this.defaultPagination,
...this.pagination,
size: this.size === 'tiny' ? 'small' : ''
}
},
boxClass () {
return {
'j-vxe-pagination': true,
'show-quick-jumper': !!this.bindProps.showQuickJumper
}
}
},
methods: {
handleChange (current, pageSize) {
this.$set(this.pagination, 'current', current)
this.$emit('change', { current, pageSize })
},
handleShowSizeChange (current, pageSize) {
this.$set(this.pagination, 'pageSize', pageSize)
this.$emit('change', { current, pageSize })
}
}
}
</script>
<style lang="less" scoped>
</style>
@@ -1,181 +0,0 @@
<template>
<a-popover :visible="visible" :placement="placement" overlayClassName="j-vxe-popover-overlay" :overlayStyle="overlayStyle">
<div class="j-vxe-popover-title" slot="title">
<div>子表</div>
<div class="j-vxe-popover-title-close" @click="close">
<a-icon type="close"/>
</div>
</div>
<template slot="content">
<transition name="fade">
<slot v-if="visible" name="subForm" :row="row" :column="column"/>
</transition>
</template>
<div ref="div" class="j-vxe-popover-div"></div>
</a-popover>
</template>
<script>
import domAlign from 'dom-align'
import { getParentNodeByTagName } from '../utils/vxeUtils'
import { cloneObject, triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'JVxeSubPopover',
data () {
return {
visible: false,
// 当前行
row: null,
column: null,
overlayStyle: {
width: null,
zIndex: 100
},
placement: 'bottom'
}
},
created () {
},
methods: {
toggle (event) {
// update-begin-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
if (document.body.clientHeight - event.$event.clientY > 350) {
this.placement = 'bottom'
} else {
this.placement = 'top'
}
// update-end-author:taoyan date:20200921 for: 弹出子表时,子表会闪一下,类似重新计算子表的位置
if (this.row == null) {
this.open(event)
} else {
this.row.id === event.row.id ? this.close() : this.reopen(event)
}
},
open (event, level = 0) {
if (level > 3) {
this.$message.error('打开子表失败')
console.warn('【JVxeSubPopover】打开子表失败')
return
}
const { row, column, $table, $event: { target } } = event
this.row = cloneObject(row)
this.column = column
let className = target.className || ''
className = typeof className === 'string' ? className : className.toString()
// 点击的是expand,不做处理
if (className.includes('vxe-table--expand-btn')) {
return
}
// 点击的是checkbox,不做处理
if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) {
return
}
// 点击的是radio,不做处理
if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) {
return
}
const table = $table.$el
const tr = getParentNodeByTagName(target, 'tr')
if (table && tr) {
const clientWidth = table.clientWidth
const clientHeight = tr.clientHeight
this.$refs.div.style.width = clientWidth + 'px'
this.$refs.div.style.height = clientHeight + 'px'
this.overlayStyle.width = Number.parseInt((clientWidth - clientWidth * 0.04) + '') + 'px'
this.overlayStyle.maxWidth = this.overlayStyle.width
// update-begin-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
// let realTable = getParentNodeByTagName(tr, 'table')
// let left = realTable.parentNode.scrollLeft
let h = event.$event.clientY
if (h) {
h = h - 140
}
const toolbar = this.$refs.div.nextSibling
domAlign(this.$refs.div, toolbar, {
points: ['tl', 'tl'],
offset: [0, h],
overflow: {
alwaysByViewport: true
}
})
// update-end-author:taoyan date:20200921 for: 子表弹出位置存在现实位置问题。
this.$nextTick(() => {
this.visible = true
this.$nextTick(() => {
triggerWindowResizeEvent()
})
})
} else {
const num = ++level
console.warn('【JVxeSubPopover】table or tr 获取失败,正在进行第 ' + num + '次重试', { event, table, tr })
window.setTimeout(() => this.open(event, num), 100)
}
},
close () {
if (this.visible) {
this.row = null
this.visible = false
}
},
reopen (event) {
this.close()
this.open(event)
}
}
}
</script>
<style scoped lang="less">
.j-vxe-popover-title {
.j-vxe-popover-title-close {
position: absolute;
right: 0;
top: 0;
width: 31px;
height: 31px;
text-align: center;
line-height: 31px;
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: color 300ms;
&:hover {
color: rgba(0, 0, 0, 0.8);
}
}
}
.j-vxe-popover-div {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 31px;
z-index: -1;
}
</style>
<style lang="less">
.j-vxe-popover-overlay.ant-popover {
.ant-popover-title {
position: relative;
}
}
.fade-enter-active,
.fade-leave-active {
opacity: 1;
transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
File diff suppressed because it is too large Load Diff
@@ -1,127 +0,0 @@
<template>
<div :class="boxClass">
<!-- 工具按钮 -->
<div class="j-vxe-tool-button div" :size="btnSize">
<slot v-if="showPrefix" name="toolbarPrefix" :size="btnSize"/>
<a-button v-if="showAdd" icon="plus" @click="trigger('add')" :disabled="disabled" type="primary">新增</a-button>
<a-button v-if="showSave" icon="save" @click="trigger('save')" :disabled="disabled">保存</a-button>
<template v-if="selectedRowIds.length > 0">
<a-popconfirm
v-if="showRemove"
:title="`确定要删除这 ${selectedRowIds.length} 项吗?`"
@confirm="trigger('remove')"
>
<a-button icon="minus" :disabled="disabled">删除</a-button>
</a-popconfirm>
<template v-if="showClearSelection">
<a-button icon="delete" @click="trigger('clearSelection')">清空选择</a-button>
</template>
</template>
<slot v-if="showSuffix" name="toolbarSuffix" :size="btnSize"/>
<a v-if="showCollapse" @click="toggleCollapse" style="margin-left: 4px">
<span>{{ collapsed ? '展开' : '收起' }}</span>
<a-icon :type="collapsed ? 'down' : 'up'"/>
</a>
</div>
</div>
</template>
<script>
export default {
name: 'JVxeToolbar',
props: {
toolbarConfig: Object,
excludeCode: Array,
size: String,
disabled: Boolean,
disabledRows: Object,
selectedRowIds: Array
},
data () {
return {
// 是否收起
collapsed: true
}
},
computed: {
boxClass () {
return {
'j-vxe-toolbar': true,
'j-vxe-toolbar-collapsed': this.collapsed
}
},
btns () {
const arr = this.toolbarConfig.btn || ['add', 'remove', 'clearSelection']
const exclude = [...this.excludeCode]
// TODO 需要将remove替换batch_delete
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
if (exclude.indexOf('batch_delete') >= 0) {
exclude.push('remove')
}
// 按钮权限 需要去掉不被授权的按钮
return arr.filter(item => {
return exclude.indexOf(item) < 0
})
},
slots () {
return this.toolbarConfig.slot || ['prefix', 'suffix']
},
showPrefix () {
return this.slots.includes('prefix')
},
showSuffix () {
return this.slots.includes('suffix')
},
showAdd () {
return this.btns.includes('add')
},
showSave () {
return this.btns.includes('save')
},
showRemove () {
return this.btns.includes('remove')
},
showClearSelection () {
if (this.btns.includes('clearSelection')) {
// 有禁用行时才显示清空选择按钮
// 因为禁用行会阻止选择行,导致无法取消全选
const length = Object.keys(this.disabledRows).length
return length > 0
}
return false
},
showCollapse () {
return this.btns.includes('collapse')
},
btnSize () {
return this.size === 'tiny' ? 'small' : null
}
},
methods: {
/** 触发事件 */
trigger (name) {
this.$emit(name)
},
// 切换展开收起
toggleCollapse () {
this.collapsed = !this.collapsed
}
}
}
</script>
<style lang="less">
.j-vxe-toolbar-collapsed {
[data-collapse] {
display: none;
}
}
.j-vxe-tool-button.div .ant-btn {
margin-right: 8px;
}
</style>
@@ -1,103 +0,0 @@
<template>
<div :class="clazz" :style="boxStyle">
<a-checkbox
ref="checkbox"
:checked="innerValue"
v-bind="cellProps"
@change="handleChange"
/>
</div>
</template>
<script>
import { neverNull } from '@/utils/util'
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeCheckboxCell',
mixins: [JVxeCellMixins],
props: {},
computed: {
bordered () {
return !!this.renderOptions.bordered
},
scrolling () {
return !!this.renderOptions.scrolling
},
clazz () {
return {
'j-vxe-checkbox': true,
'no-animation': this.scrolling
}
},
boxStyle () {
const style = {}
// 如果有边框且未设置align属性,就强制居中
if (this.bordered && !this.originColumn.align) {
style['text-align'] = 'center'
}
return style
}
},
methods: {
handleChange (event) {
this.handleChangeCommon(event.target.checked)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: {
visible: true
},
getValue (value) {
const { params: col } = this.column
// 处理 customValue
if (Array.isArray(col.customValue)) {
const customValue = getCustomValue(col)
if (typeof value === 'boolean') {
return value ? customValue[0] : customValue[1]
} else {
return value
}
} else {
return value
}
},
setValue (value) {
const { params: col } = this.column
// 判断是否设定了customValue(自定义值)
if (Array.isArray(col.customValue)) {
const customValue = getCustomValue(col)
return neverNull(value).toString() === customValue[0].toString()
} else {
return !!value
}
},
createValue ({ column }) {
const { params: col } = column
if (Array.isArray(col.customValue)) {
const customValue = getCustomValue(col)
return col.defaultChecked ? customValue[0] : customValue[1]
} else {
return !!col.defaultChecked
}
}
}
}
function getCustomValue (col) {
const customTrue = neverNull(col.customValue[0], true)
const customFalse = neverNull(col.customValue[1], false)
return [customTrue, customFalse]
}
</script>
<style lang="less">
// 关闭动画,防止滚动时动态赋值出现问题
.j-vxe-checkbox.no-animation {
.ant-checkbox-inner,
.ant-checkbox-inner::after {
transition: none !important;
}
}
</style>
@@ -1,68 +0,0 @@
<template>
<a-date-picker
ref="datePicker"
:value="innerDateValue"
allowClear
:format="dateFormat"
:showTime="isDatetime"
dropdownClassName="j-vxe-date-picker"
style="min-width: 0;"
v-bind="cellProps"
@change="handleChange"
/>
</template>
<script>
import moment from 'moment'
import { JVXETypes } from '@comp/jero/JVxeTable'
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeDateCell',
mixins: [JVxeCellMixins],
props: {},
data () {
return {
innerDateValue: null
}
},
computed: {
isDatetime () {
return this.$type === JVXETypes.datetime
},
dateFormat () {
const format = this.originColumn.format
return format || (this.isDatetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD')
}
},
watch: {
innerValue: {
immediate: true,
handler (val) {
if (val == null || val === '') {
this.innerDateValue = null
} else {
this.innerDateValue = moment(val, this.dateFormat)
}
}
}
},
methods: {
handleChange (mom, dateStr) {
this.handleChangeCommon(dateStr)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived (event) {
dispatchEvent.call(this, event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event))
}
}
}
}
</script>
<style scoped>
</style>
@@ -1,128 +0,0 @@
<template>
<div>
<a-input
v-show="!departIds"
@click="openSelect"
placeholder="请点击选择部门"
v-model="departNames"
readOnly
:disabled="componentDisabled"
class="jvxe-select-input">
<a-icon slot="prefix" type="cluster" title="部门选择控件"/>
</a-input>
<j-select-depart-modal
ref="innerDepartSelectModal"
:modal-width="modalWidth"
:multi="multi"
:rootOpened="rootOpened"
:depart-id="departIds"
@ok="handleOK"
@initComp="initComp"/>
<span style="display: inline-block;height:100%;padding-left:14px" v-if="departIds" >
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ departNames }}</span>
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
</span>
</div>
</template>
<script>
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import JSelectDepartModal from '@/components/jerobiz/modal/JSelectDepartModal'
export default {
name: 'JVxeDepartSelectCell',
mixins: [JVxeCellMixins],
components: {
JSelectDepartModal
},
data () {
return {
departNames: '',
departIds: '',
selectedOptions: [],
customReturnField: 'id'
}
},
computed: {
custProps () {
const { departIds, originColumn: col, caseId, cellProps } = this
return {
...cellProps,
value: departIds,
field: col.field || col.key,
groupId: caseId,
class: 'jvxe-select'
}
},
componentDisabled () {
return this.cellProps.disabled === true
},
modalWidth () {
if (this.cellProps.modalWidth) {
return this.cellProps.modalWidth
} else {
return 500
}
},
multi () {
console.log(this.cellProps)
return this.cellProps.multi !== false
},
rootOpened () {
return this.cellProps.open !== false
}
},
watch: {
innerValue: {
immediate: true,
handler (val) {
if (val == null || val === '') {
this.departIds = ''
} else {
this.departIds = val
}
}
}
},
methods: {
openSelect () {
this.$refs.innerDepartSelectModal.show()
},
handleEmpty () {
this.handleOK('')
},
handleOK (rows, idstr) {
// let value = ''
if (!rows && rows.length <= 0) {
this.departNames = ''
this.departIds = ''
} else {
// value = rows.map(row => row[this.customReturnField]).join(',')
this.departNames = rows.map(row => row.departName).join(',')
this.departIds = idstr
}
this.handleChangeCommon(this.departIds)
},
initComp (departNames) {
this.departNames = departNames
},
handleChange (value) {
this.handleChangeCommon(value)
}
},
enhanced: {
switches: {
visible: true
},
translate: {
enabled: false
}
}
}
</script>
<style scoped>
/deep/ .jvxe-select-input .ant-input {
border: none !important;
}
</style>
@@ -1,138 +0,0 @@
<template>
<a-dropdown :trigger="['click']">
<div class="j-vxe-ds-icons">
<a-icon type="align-left" />
<a-icon type="align-right" />
</div>
<!-- <div class="j-vxe-ds-btns">-->
<!-- <a-button icon="caret-up" size="small" :disabled="disabledMoveUp" @click="handleRowMoveUp"/>-->
<!-- <a-button icon="caret-down" size="small" :disabled="disabledMoveDown" @click="handleRowMoveDown"/>-->
<!-- </div>-->
<a-menu slot="overlay">
<a-menu-item key="0" :disabled="disabledMoveUp" @click="handleRowMoveUp">向上移</a-menu-item>
<a-menu-item key="1" :disabled="disabledMoveDown" @click="handleRowMoveDown">向下移</a-menu-item>
<a-menu-divider />
<a-menu-item key="3" @click="handleRowInsertDown">插入一行</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<script>
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeDragSortCell',
mixins: [JVxeCellMixins],
computed: {
// 排序结果保存字段
dragSortKey () {
return this.renderOptions.dragSortKey || 'orderNum'
},
disabledMoveUp () {
return this.rowIndex === 0
},
disabledMoveDown () {
return this.rowIndex === (this.fullDataLength - 1)
}
},
methods: {
/** 向上移 */
handleRowMoveUp () {
// event.target.blur()
if (!this.disabledMoveUp) {
this.trigger('rowMoveUp', this.rowIndex)
}
},
/** 向下移 */
handleRowMoveDown () {
// event.target.blur()
if (!this.disabledMoveDown) {
this.trigger('rowMoveDown', this.rowIndex)
}
},
/** 插入一行 */
handleRowInsertDown () {
this.trigger('rowInsertDown', this.rowIndex)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
// 【功能开关】
switches: {
editRender: false
}
}
}
</script>
<style lang="less">
.j-vxe-ds-icons {
position: relative;
/*cursor: move;*/
cursor: pointer;
width: 14px;
height: 100%;
display: inline-block;
.anticon-align-left,
.anticon-align-right {
position: absolute;
top: 30%;
}
.anticon-align-left {
left: 0;
}
.anticon-align-right {
right: 0;
}
}
.j-vxe-ds-btns {
position: relative;
cursor: pointer;
width: 24px;
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-content: center;
.ant-btn {
border: none;
z-index: 0;
padding: 0;
width: 100%;
/*height: 30%;*/
height: 40%;
display: block;
border-radius: 0;
&:hover {
z-index: 1;
/* height: 40%;*/
/* & .anticon-caret-up,*/
/* & .anticon-caret-down {*/
/* top: 2px;*/
/* }*/
}
&:last-child {
margin-top: -1px;
}
& .anticon-caret-up,
& .anticon-caret-down {
vertical-align: top;
position: relative;
top: 0;
transition: top 0.3s;
}
}
}
</style>
@@ -1,87 +0,0 @@
<template>
<a-input
ref="input"
:value="innerValue"
v-bind="cellProps"
@blur="handleBlur"
@change="handleChange"
/>
</template>
<script>
import { JVXETypes } from '@/components/jero/JVxeTable'
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
const NumberRegExp = /^-?\d+\.?\d*$/
export default {
name: 'JVxeInputCell',
mixins: [JVxeCellMixins],
methods: {
/** 处理change事件 */
handleChange (event) {
const { $type } = this
const { target } = event
let { value, selectionStart } = target
let change = true
if ($type === JVXETypes.inputNumber) {
// 判断输入的值是否匹配数字正则表达式,不匹配就还原
if (!NumberRegExp.test(value) && (value !== '' && value !== '-')) {
change = false
value = this.innerValue
target.value = value || ''
if (typeof selectionStart === 'number') {
target.selectionStart = selectionStart - 1
target.selectionEnd = selectionStart - 1
}
}
}
// 触发事件,存储输入的值
if (change) {
this.handleChangeCommon(value)
}
if ($type === JVXETypes.inputNumber) {
// this.recalcOneStatisticsColumn(col.key)
}
},
/** 处理blur失去焦点事件 */
handleBlur (event) {
const { $type } = this
const { target } = event
// 判断输入的值是否匹配数字正则表达式,不匹配就置空
if ($type === JVXETypes.inputNumber) {
if (!NumberRegExp.test(target.value)) {
target.value = ''
} else {
target.value = Number.parseFloat(target.value)
}
this.handleChangeCommon(target.value)
}
this.handleBlurCommon(target.value)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
installOptions: {
// 自动聚焦的 class 类名
autofocus: '.ant-input'
},
getValue (value) {
if (this.$type === JVXETypes.inputNumber && typeof value === 'string') {
if (NumberRegExp.test(value)) {
return Number.parseFloat(value)
}
}
return value
}
}
}
</script>
<style scoped>
</style>
@@ -1,42 +0,0 @@
<template>
<reload-effect
:vNode="innerValue"
:effect="reloadEffect"
@effect-end="handleEffectEnd"
/>
</template>
<script>
import ReloadEffect from './ReloadEffect'
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeNormalCell',
mixins: [JVxeCellMixins],
components: { ReloadEffect },
computed: {
reloadEffectRowKeysMap () {
return this.renderOptions.reloadEffectRowKeysMap
},
reloadEffect () {
return (this.renderOptions.reloadEffect && this.reloadEffectRowKeysMap[this.row.id]) === true
}
},
methods: {
// 特效结束
handleEffectEnd () {
this.$delete(this.reloadEffectRowKeysMap, this.row.id)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: {
editRender: false
}
}
}
</script>
<style scoped>
</style>
@@ -1,60 +0,0 @@
<template>
<a-progress
:class="clazz"
:percent="innerValue"
size="small"
v-bind="cellProps"
/>
</template>
<script>
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
// JVxe 进度条组件
export default {
name: 'JVxeProgressCell',
mixins: [JVxeCellMixins],
data () {
return {}
},
computed: {
clazz () {
return {
'j-vxe-progress': true,
'no-animation': this.scrolling
}
},
scrolling () {
return !!this.renderOptions.scrolling
}
},
methods: {},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: {
editRender: false
},
setValue (value) {
try {
if (typeof value !== 'number') {
return Number.parseFloat(value)
} else {
return value
}
} catch {
return 0
}
}
}
}
</script>
<style scoped lang="less">
// 关闭进度条的动画,防止滚动时动态赋值出现问题
.j-vxe-progress.no-animation {
/deep/ .ant-progress-success-bg,
/deep/ .ant-progress-bg {
transition: none !important;
}
}
</style>
@@ -1,219 +0,0 @@
<template>
<a-select
ref="select"
:value="innerValue"
allowClear
:filterOption="handleSelectFilterOption"
v-bind="selectProps"
style="width: 100%;"
@blur="handleBlur"
@change="handleChange"
@search="handleSearchSelect">
<div v-if="loading" slot="notFoundContent">
<a-icon type="loading" />
<span>&nbsp;加载中</span>
</div>
<template v-for="option of selectOptions">
<a-select-option :key="option.value" :value="option.value" :disabled="option.disabled">
<span>{{ option.text || option.label || option.title || option.value }}</span>
</a-select-option>
</template>
</a-select>
</template>
<script>
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import { JVXETypes } from '@comp/jero/JVxeTable'
import { filterDictText } from '@comp/dict/JDictSelectUtil'
export default {
name: 'JVxeSelectCell',
mixins: [JVxeCellMixins],
data () {
return {
loading: false,
// 异步加载的options(用于多级联动)
asyncOptions: null
}
},
computed: {
selectOptions () {
if (this.asyncOptions) {
return this.asyncOptions
}
const { linkage } = this.renderOptions
if (linkage) {
return this.handleComputedSelectOptions(linkage)
}
return this.originColumn.options
},
// 下拉选项
selectProps () {
const props = { ...this.cellProps }
// 判断select是否允许输入
const { allowSearch, allowInput } = this.originColumn
if (allowInput === true || allowSearch === true) {
props.showSearch = true
}
return props
}
},
created () {
const multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
const search = [JVXETypes.selectSearch, JVXETypes.sel_search]
if (multiple.includes(this.$type)) {
// 处理多选
const props = this.originColumn.props || {}
props.mode = 'multiple'
props.maxTagCount = 1
this.$set(this.originColumn, 'props', props)
} else if (search.includes(this.$type)) {
// 处理搜索
this.$set(this.originColumn, 'allowSearch', true)
}
},
methods: {
handleChange (value) {
// 处理下级联动
const linkage = this.renderOptions.linkage
if (linkage) {
linkage.linkageSelectChange(this.row, this.originColumn, linkage.config, value)
}
this.handleChangeCommon(value)
},
/** 处理blur失去焦点事件 */
handleBlur (value) {
const { allowInput, options } = this.originColumn
if (allowInput === true) {
// 删除无用的因搜索(用户输入)而创建的项
if (typeof value === 'string') {
const indexes = []
options.forEach((option, index) => {
if (option.value.toLocaleString() === value.toLocaleString()) {
delete option.searchAdd
} else if (option.searchAdd === true) {
indexes.push(index)
}
})
// 翻转删除数组中的项
for (const index of indexes.reverse()) {
options.splice(index, 1)
}
}
}
this.handleBlurCommon(value)
},
/** 用于搜索下拉框中的内容 */
handleSelectFilterOption (input, option) {
const { allowSearch, allowInput } = this.originColumn
if (allowSearch === true || allowInput === true) {
// update-begin-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
return option.componentOptions.children[0].children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
// update-end-author:taoyan date:20200820 for:【专项任务】大连项目反馈行编辑问题处理 下拉框搜索
}
return true
},
/** select 搜索时的事件,用于动态添加options */
handleSearchSelect (value) {
const { allowSearch, allowInput, options } = this.originColumn
if (allowSearch !== true && allowInput === true) {
// 是否找到了对应的项,找不到则添加这一项
let flag = false
for (const option of options) {
if (option.value.toLocaleString() === value.toLocaleString()) {
flag = true
break
}
}
// !!value :不添加空值
if (!flag && !!value) {
// searchAdd 是否是通过搜索添加的
options.push({ title: value, value: value, searchAdd: true })
}
}
},
handleComputedSelectOptions (linkage) {
const { getLinkageOptionsSibling, config } = linkage
const res = getLinkageOptionsSibling(this.row, this.originColumn, config, true)
// 当返回Promise时,说明是多级联动
if (res instanceof Promise) {
this.loading = true
res.then(opt => {
this.asyncOptions = opt
this.loading = false
}).catch(e => {
console.error(e)
this.loading = false
})
} else {
this.asyncOptions = null
return res
}
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
aopEvents: {
editActived (event) {
dispatchEvent.call(this, event, 'ant-select')
}
},
translate: {
enabled: true,
async handler (value) {
let options
const { linkage } = this.renderOptions
// 判断是否是多级联动,如果是就通过接口异步翻译
if (linkage) {
const { getLinkageOptionsSibling, config } = linkage
options = getLinkageOptionsSibling(this.row, this.originColumn, config, true)
if (options instanceof Promise) {
return new Promise(resolve => {
options.then(opt => {
resolve(filterDictText(opt, value))
})
})
}
} else {
options = (this.column.params || {}).options
}
return filterDictText(options, value)
}
},
getValue (value) {
if (Array.isArray(value)) {
return value.join(',')
} else {
return value
}
},
setValue (value) {
const { column: { params: col }, params: { $table } } = this
// 判断是否是多选
if ((col.props || {}).mode === 'multiple') {
$table.$set(col.props, 'maxTagCount', 1)
}
if (value != null && value !== '') {
if (typeof value === 'string') {
return value === '' ? [] : value.split(',')
}
return value
} else {
return undefined
}
}
}
}
</script>
<style scoped>
</style>
@@ -1,46 +0,0 @@
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
// 插槽
export default {
name: 'JVxeSlotCell',
mixins: [JVxeCellMixins],
computed: {
slotProps () {
return {
value: this.innerValue,
row: this.row,
column: this.originColumn,
params: this.params,
$table: this.params.$table,
rowId: this.params.rowid,
index: this.params.rowIndex,
rowIndex: this.params.rowIndex,
columnIndex: this.params.columnIndex,
target: this.renderOptions.target,
caseId: this.renderOptions.target.caseId,
scrolling: this.renderOptions.scrolling,
reloadEffect: this.renderOptions.reloadEffect,
triggerChange: (v) => this.handleChangeCommon(v)
}
}
},
render (h) {
const { slot } = this.renderOptions
if (slot) {
return h('div', {}, slot(this.slotProps))
} else {
return h('div')
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: {
editRender: false
}
}
}
// :isNotPass="notPassedIds.includes(col.key+row.id)"
@@ -1,145 +0,0 @@
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
// tags 组件的显示组件
export const TagsSpanCell = {
name: 'JVxeTagsCell',
mixins: [JVxeCellMixins],
data () {
return {
innerTags: []
}
},
watch: {
innerValue: {
immediate: true,
handler (value) {
if (value !== this.innerTags.join(';')) {
const rv = replaceValue(value)
this.innerTags = rv.split(';')
this.handleChangeCommon(rv)
}
}
}
},
methods: {
renderTags (h) {
const tags = []
for (const tag of this.innerTags) {
if (tag) {
const tagProps = {}
const tagStyle = {}
const setTagColor = this.originColumn.setTagColor
if (typeof setTagColor === 'function') {
/**
* 设置 tag 颜色
*
* @param event 包含的字段:
* event.tagValue 当前tag的值
* event.value 当前原始值
* event.row 当前行的所有值
* event.column 当前列的配置
* event.column.own 当前列的原始配置
* @return Array | String 可以返回一个数组,数据第一项是tag背景颜色,第二项是字体颜色。也可以返回一个字符串,即tag背景颜色
*/
const color = setTagColor({
tagValue: tag,
value: this.innerValue,
row: this.row,
column: this.column
})
if (Array.isArray(color)) {
tagProps.color = color[0]
tagStyle.color = color[1]
} else if (color && typeof color === 'string') {
tagProps.color = color
}
}
tags.push(h('a-tag', {
props: tagProps,
style: tagStyle
}, [tag]))
}
}
return tags
}
},
render (h) {
return h('div', {}, [
this.renderTags(h)
])
}
}
// tags 组件的输入框
export const TagsInputCell = {
name: 'JVxeTagsInputCell',
mixins: [JVxeCellMixins],
data () {
return {
innerTagValue: ''
}
},
watch: {
innerValue: {
immediate: true,
handler (value) {
if (value !== this.innerTagValue) {
this.handleInputChange(value)
}
}
}
},
methods: {
handleInputChange (value, event) {
this.innerTagValue = replaceValue(value, event)
this.handleChangeCommon(this.innerTagValue)
return this.innerTagValue
}
},
render (h) {
return h('a-input', {
props: {
value: this.innerValue,
...this.cellProps
},
on: {
change: (event) => {
const { target, target: { value } } = event
const newValue = this.handleInputChange(value, event)
if (newValue !== value) {
target.value = newValue
}
}
}
})
}
}
// 将值每隔两位加上一个分号
function replaceValue (value, event) {
if (value) {
// 首先去掉现有的分号
value = value.replace(/;/g, '')
// 然后再遍历添加分号
let rv = ''
const splitArr = value.split('')
let count = 0
splitArr.forEach((val, index) => {
rv += val
const position = index + 1
if (position % 2 === 0 && position < splitArr.length) {
count++
rv += ';'
}
})
if (event && count > 0) {
const { target, target: { selectionStart } } = event
target.selectionStart = selectionStart + count
target.selectionEnd = selectionStart + count
}
return rv
}
return ''
}
@@ -1,36 +0,0 @@
<template>
<j-input-pop
:value="innerValue"
:width="300"
:height="210"
v-bind="cellProps"
style="width: 100%;"
@change="handleChangeCommon"
/>
</template>
<script>
import JInputPop from '@/components/jero/minipop/JInputPop'
import JVxeCellMixins, { dispatchEvent } from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
export default {
name: 'JVxeTextareaCell',
mixins: [JVxeCellMixins],
components: { JInputPop },
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
installOptions: {
autofocus: '.ant-input'
},
aopEvents: {
editActived (event) {
dispatchEvent.call(this, event, 'anticon-fullscreen')
}
}
}
}
</script>
<style scoped>
</style>
@@ -1,190 +0,0 @@
<template>
<div>
<template v-if="hasFile">
<template v-for="(file, fileKey) of [innerFile || {}]">
<a-input :key="fileKey" :readOnly="true" :value="file.name">
<template slot="addonBefore" style="width: 30px">
<a-tooltip v-if="file.status === 'uploading'" :title="`上传中(${Math.floor(file.percent)}%)`">
<a-icon type="loading" />
</a-tooltip>
<a-tooltip v-else-if="file.status === 'done'" title="上传完成">
<a-icon type="check-circle" style="color:#00DB00;" />
</a-tooltip>
<a-tooltip v-else :title="file.message||'上传失败'">
<a-icon type="exclamation-circle" style="color:red;" />
</a-tooltip>
</template>
<span v-if="file.status === 'uploading'" slot="addonAfter">{{ Math.floor(file.percent) }}%</span>
<template v-else-if="originColumn.allowDownload !== false || originColumn.allowRemove !== false" slot="addonAfter">
<a-dropdown :trigger="['click']" placement="bottomRight">
<a-tooltip title="操作">
<a-icon type="setting" style="cursor: pointer;" />
</a-tooltip>
<a-menu slot="overlay">
<!-- <a-menu-item @click="handleClickPreviewFile">-->
<!-- <span><a-icon type="eye"/>&nbsp;预览</span>-->
<!-- </a-menu-item>-->
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
<span><a-icon type="download" />&nbsp;下载</span>
</a-menu-item>
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
<span><a-icon type="delete" />&nbsp;删除</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
</a-input>
</template>
</template>
<a-upload
v-show="!hasFile"
name="file"
:data="{'isup': 1}"
:multiple="false"
:action="uploadAction"
:headers="uploadHeaders"
:showUploadList="false"
v-bind="cellProps"
@change="handleChangeUpload">
<a-button icon="upload">{{ originColumn.btnText || '点击上传' }}</a-button>
</a-upload>
</div>
</template>
<script>
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { getFileAccessHttpUrl } from '@api/manage'
export default {
name: 'JVxeUploadCell',
mixins: [JVxeCellMixins],
props: {},
data () {
return {
innerFile: null
}
},
computed: {
/** upload headers */
uploadHeaders () {
const { originColumn: col } = this
const headers = {}
if (col.token === true) {
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
}
return headers
},
hasFile () {
return !!this.innerFile
},
uploadAction () {
if (this.originColumn.action) {
return this.originColumn.action
}
return window._CONFIG.domianURL + '/sys/common/upload'
}
},
watch: {
innerValue: {
immediate: true,
handler () {
if (this.innerValue) {
this.innerFile = this.innerValue
} else {
this.innerFile = null
}
}
}
},
methods: {
handleChangeUpload (info) {
const { originColumn: col } = this
const { file } = info
const value = {
name: file.name,
type: file.type,
size: file.size,
status: file.status,
percent: file.percent
}
if (col.responseName && file.response) {
value.responseName = file.response[col.responseName]
}
if (file.status === 'done') {
if (typeof file.response.success === 'boolean') {
if (file.response.success) {
value.path = col.responseName ? file.response[col.responseName] : file.response.result.url
} else {
value.status = 'error'
value.message = file.response.message || '未知错误'
}
} else {
// 考虑到如果设置action上传路径为非jeecg-boot后台,可能不会返回 success 属性的情况,就默认为成功
value.path = file.response[col.responseName]
}
} else if (file.status === 'error') {
value.message = file.response.message || '未知错误'
}
this.innerFile = value
// 上传完成后触发通用change事件,将值抛出去
if (file.status === 'done') {
this.handleChangeCommon(this.innerFile)
}
},
// handleClickPreviewFile(id) {
// this.$message.info('尚未实现')
// },
handleClickDownloadFile () {
const { path } = this.value || {}
if (path) {
const url = getFileAccessHttpUrl(path)
window.open(url)
}
},
handleClickDeleteFile () {
this.handleChangeCommon(null)
}
},
// 【组件增强】注释详见:JVxeCellMixins.js
enhanced: {
switches: { visible: true },
getValue: value => fileGetValue(value),
setValue: value => fileSetValue(value)
}
}
function fileGetValue (value) {
if (value && value.path) {
return value.path
}
return value
}
function fileSetValue (value) {
if (value) {
const first = value.split(',')[0]
const name = first.substring(first.lastIndexOf('/') + 1)
return {
name: name,
path: value,
status: 'done'
}
}
return value
}
</script>
<style scoped>
</style>
@@ -1,129 +0,0 @@
<template>
<div>
<a-input
v-show="!userIds"
@click="openSelect"
placeholder="请选择用户"
v-model="userNames"
readOnly
class="jvxe-select-input"
:disabled="componentDisabled">
<a-icon slot="prefix" type="user" title="用户选择控件"/>
</a-input>
<j-select-user-by-dep-modal
ref="selectModal"
:modal-width="modalWidth"
:multi="multi"
:user-ids="userIds"
@ok="selectOK"
@initComp="initComp"/>
<span style="display: inline-block;height:100%;padding-left:14px" v-if="userIds" >
<span @click="openSelect" style="display: inline-block;vertical-align: middle">{{ userNames }}</span>
<a-icon style="margin-left:5px;vertical-align: middle" type="close-circle" @click="handleEmpty" title="清空"/>
</span>
</div>
<!-- <j-select-user-by-dep
v-bind="custProps"
@change="handleChange"
:trigger-change="true">
</j-select-user-by-dep>-->
</template>
<script>
import JVxeCellMixins from '@/components/jero/JVxeTable/mixins/JVxeCellMixins'
import JSelectUserByDepModal from '@/components/jerobiz/modal/JSelectUserByDepModal'
export default {
name: 'JVxeUserSelectCell',
mixins: [JVxeCellMixins],
components: { JSelectUserByDepModal },
data () {
return {
userIds: '',
userNames: '',
innerUserValue: '',
selectedOptions: []
}
},
computed: {
custProps () {
const { userIds, originColumn: col, caseId, cellProps } = this
return {
...cellProps,
value: userIds,
field: col.field || col.key,
groupId: caseId,
class: 'jvxe-select'
}
},
componentDisabled () {
console.log('333', this.cellProps)
return this.cellProps.disabled === true
},
modalWidth () {
if (this.cellProps.modalWidth) {
return this.cellProps.modalWidth
} else {
return 1250
}
},
multi () {
return this.cellProps.multi !== false
}
},
watch: {
innerValue: {
immediate: true,
handler (val) {
if (val == null || val === '') {
this.userIds = ''
} else {
this.userIds = val
}
}
}
},
methods: {
openSelect () {
this.$refs.selectModal.showModal()
},
selectOK (rows, idstr) {
console.log('当前选中用户', rows)
console.log('当前选中用户ID', idstr)
if (!rows) {
this.userNames = ''
this.userIds = ''
} else {
let temp = ''
for (const item of rows) {
temp += ',' + item.realname
}
this.userNames = temp.substring(1)
this.userIds = idstr
}
this.handleChangeCommon(this.userIds)
},
handleEmpty () {
this.selectOK('')
},
initComp (userNames) {
this.userNames = userNames
}
},
enhanced: {
switches: {
visible: true
},
translate: {
enabled: false
}
}
}
</script>
<style scoped>
/deep/ .jvxe-select-input .ant-input {
border: none !important;
}
</style>
@@ -1,84 +0,0 @@
import '../../less/reload-effect.less'
import { randomString } from '@/utils/util'
// 修改数据特效
export default {
props: {
vNode: null,
// 是否启用特效
effect: Boolean
},
data () {
return {
// vNode: null,
innerEffect: false,
// 应付同时多个特效
effectIdx: 0,
effectList: []
}
},
watch: {
vNode: {
deep: true,
immediate: true,
handler (vNode, old) {
this.innerEffect = this.effect
if (this.innerEffect && old != null) {
const topLayer = this.renderSpan(old, 'top')
this.effectList.push(topLayer)
}
}
}
},
methods: {
// 条件渲染内容 span
renderVNode () {
if (this.vNode == null) {
return null
}
const bottom = this.renderSpan(this.vNode, 'bottom')
// 启用了特效,并且有旧数据,就渲染特效顶层
if (this.innerEffect && this.effectList.length > 0) {
this.$emit('effect-begin')
// 1.4s 以后关闭特效
window.setTimeout(() => {
const item = this.effectList[this.effectIdx]
if (item && item.elm) {
// 特效结束后,展示先把 display 设为 none,而不是直接删掉该元素,
// 目的是为了防止页面重新渲染,导致动画重置
item.elm.style.display = 'none'
}
// 当所有的层级动画都结束时,再删掉所有元素
if (++this.effectIdx === this.effectList.length) {
this.innerEffect = false
this.effectIdx = 0
this.effectList = []
this.$emit('effect-end')
}
}, 1400)
return [this.effectList, bottom]
} else {
return bottom
}
},
// 渲染内容 span
renderSpan (vNode, layer) {
const options = {
key: layer + this.effectIdx + randomString(6),
class: ['j-vxe-reload-effect-span', `layer-${layer}`],
style: {}
}
if (layer === 'top') {
// 最新渲染的在下面
options.style['z-index'] = (9999 - this.effectIdx)
}
return this.$createElement('span', options, [vNode])
}
},
render (h) {
return h('div', {
class: ['j-vxe-reload-effect-box']
}, [this.renderVNode()])
}
}
-51
View File
@@ -1,51 +0,0 @@
import * as jvxeTypes from './jvxeTypes'
import { installCell, mapCell } from './install'
import JVxeTable from './components/JVxeTable'
import JVxeSlotCell from './components/cells/JVxeSlotCell'
import JVxeNormalCell from './components/cells/JVxeNormalCell'
import JVxeInputCell from './components/cells/JVxeInputCell'
import JVxeDateCell from './components/cells/JVxeDateCell'
import JVxeSelectCell from './components/cells/JVxeSelectCell'
import JVxeCheckboxCell from './components/cells/JVxeCheckboxCell'
import JVxeUploadCell from './components/cells/JVxeUploadCell'
import { TagsInputCell, TagsSpanCell } from './components/cells/JVxeTagsCell'
import JVxeProgressCell from './components/cells/JVxeProgressCell'
import JVxeTextareaCell from './components/cells/JVxeTextareaCell'
import JVxeDragSortCell from './components/cells/JVxeDragSortCell'
import JVxeDepartSelectCell from './components/cells/JVxeDepartSelectCell'
import JVxeUserSelectCell from './components/cells/JVxeUserSelectCell'
// update--begin--autor:lvdandan-----date:20201216------forJVxeTable--JVXETypes 【online】代码结构调整,便于online打包
// 组件类型
export const JVXETypes = jvxeTypes.JVXETypes
// update--end--autor:lvdandan-----date:20201216------forJVxeTable--JVXETypes 【online】代码结构调整,便于online打包
// 注册自定义组件
export const AllCells = {
...mapCell(JVXETypes.normal, JVxeNormalCell),
...mapCell(JVXETypes.input, JVxeInputCell),
...mapCell(JVXETypes.inputNumber, JVxeInputCell),
...mapCell(JVXETypes.checkbox, JVxeCheckboxCell),
...mapCell(JVXETypes.select, JVxeSelectCell),
...mapCell(JVXETypes.selectSearch, JVxeSelectCell), // 下拉搜索
...mapCell(JVXETypes.selectMultiple, JVxeSelectCell), // 下拉多选
...mapCell(JVXETypes.date, JVxeDateCell),
...mapCell(JVXETypes.datetime, JVxeDateCell),
...mapCell(JVXETypes.upload, JVxeUploadCell),
...mapCell(JVXETypes.textarea, JVxeTextareaCell),
...mapCell(JVXETypes.tags, TagsInputCell, TagsSpanCell),
...mapCell(JVXETypes.progress, JVxeProgressCell),
...mapCell(JVXETypes.rowDragSort, JVxeDragSortCell),
...mapCell(JVXETypes.slot, JVxeSlotCell),
...mapCell(JVXETypes.departSelect, JVxeDepartSelectCell),
...mapCell(JVXETypes.userSelect, JVxeUserSelectCell)
/* hidden 是特殊的组件,不在这里注册 */
}
export { installCell, mapCell }
export default JVxeTable
-105
View File
@@ -1,105 +0,0 @@
import Vue from 'vue'
import { getEventPath, evil } from '@/utils/util'
import JVxeTable, { AllCells, JVXETypes } from './index'
import './less/j-vxe-table.less'
// 引入 vxe-table
import 'xe-utils'
import VXETable, { Grid } from 'vxe-table'
import VXETablePluginAntd from 'vxe-table-plugin-antd'
import 'vxe-table/lib/index.css'
import 'vxe-table-plugin-antd/dist/style.css'
import { getEnhancedMixins, installAllCell, installOneCell } from '@/components/jero/JVxeTable/utils/cellUtils'
// VxeGrid所有的方法映射
const VxeGridMethodsMap = {}
Object.keys(Grid.methods).forEach(key => {
// 使用eval可以避免闭包(但是要注意不要写es6的代码)
VxeGridMethodsMap[key] = evil(`(function(){return this.$refs.vxe.${key}.apply(this.$refs.vxe,arguments)})`)
})
// 将Grid所有的方法都映射(继承)到JVxeTable上
JVxeTable.methods = Object.assign({}, VxeGridMethodsMap, JVxeTable.methods)
// VXETable 全局配置
const VXETableSettings = {
// z-index 起始值
zIndex: 1000,
table: {
validConfig: {
// 校验提示方式:强制使用tooltip
message: 'tooltip'
}
}
}
// 执行注册方法
Vue.use(VXETable, VXETableSettings)
VXETable.use(VXETablePluginAntd)
Vue.component(JVxeTable.name, JVxeTable)
// 注册自定义组件
installAllCell(VXETable)
// 添加事件拦截器 event.clearActived
// 比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
VXETable.interceptor.add('event.clearActived', function (params) {
// 获取组件增强
const col = params.column.params || {}
const interceptor = getEnhancedMixins(col.$type, 'interceptor')
// 执行增强
const flag = interceptor['event.clearActived'].apply(this, arguments)
if (flag === false) {
return false
}
// 此处判断一下event是否存在,防止后续内容报错
const path = params.$event ? getEventPath(params.$event) : []
for (const p of path) {
let className = p.className || ''
className = typeof className === 'string' ? className : className.toString()
/* --- 特殊处理以下组件,点击以下标签时不清空编辑状态 --- */
// 点击的标签是JInputPop
if (className.includes('j-input-pop')) {
return false
}
// 点击的标签是JPopup的弹出层、部门选择、用户选择
if (className.includes('j-popup-modal') || className.includes('j-depart-select-modal') || className.includes('j-user-select-modal')) {
return false
}
// 执行增强
const flag = interceptor['event.clearActived.className'].apply(this, [className, ...arguments])
if (flag === false) {
return false
}
}
})
/**
* 注册map
* @param type 类型
* @param cell 输入组件
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
*/
export function mapCell (type, cell, span) {
const cells = { [type]: cell }
if (span) {
cells[type + ':span'] = span
}
return cells
}
/**
* 注册自定义组件
*
* @param type 类型
* @param cell 输入组件
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
*/
export function installCell (type, cell, span) {
const exclude = [JVXETypes.rowNumber, JVXETypes.rowCheckbox, JVXETypes.rowRadio, JVXETypes.rowExpand, JVXETypes.rowDragSort]
if (exclude.includes(type)) {
throw new Error(`【installCell】不能使用"${type}"作为组件的type,因为这是关键字。`)
}
Object.assign(AllCells, mapCell(type, cell, span))
installOneCell(VXETable, type)
}
@@ -1,43 +0,0 @@
// 组件类型
export const JVXETypes = {
// 为了防止和 vxe 内置的类型冲突,所以加上一个前缀
// 前缀是自动加的,代码中直接用就行(JVXETypes.input
_prefix: 'j-',
// 行号列
rowNumber: 'row-number',
// 选择列
rowCheckbox: 'row-checkbox',
// 单选列
rowRadio: 'row-radio',
// 展开列
rowExpand: 'row-expand',
// 上下排序
rowDragSort: 'row-drag-sort',
input: 'input',
inputNumber: 'inputNumber',
textarea: 'textarea',
select: 'select',
date: 'date',
datetime: 'datetime',
checkbox: 'checkbox',
upload: 'upload',
// 下拉搜索
selectSearch: 'select-search',
// 下拉多选
selectMultiple: 'select-multiple',
// 进度条
progress: 'progress',
// 部门选择
departSelect: 'sel_depart',
// 用户选择
userSelect: 'sel_user',
// 拖轮Tags(暂无用)
tags: 'tags',
slot: 'slot',
normal: 'normal',
hidden: 'hidden'
}
@@ -1,59 +0,0 @@
@import "size/tiny";
.j-vxe-table-box {
// 工具栏
.j-vxe-toolbar {
margin-bottom: 8px;
}
// 分页器
.j-vxe-pagination {
margin-top: 8px;
text-align: right;
.ant-pagination-options-size-changer.ant-select {
margin-right: 0;
}
&.show-quick-jumper {
.ant-pagination-options-size-changer.ant-select {
margin-right: 8px;
}
}
}
// 更改 header 底色
.vxe-table.border--default .vxe-table--header-wrapper,
.vxe-table.border--full .vxe-table--header-wrapper,
.vxe-table.border--outer .vxe-table--header-wrapper {
background-color: #FFFFFF;
}
}
// 更改 tooltip 校验失败的颜色
.vxe-table--tooltip-wrapper.vxe-table--valid-error {
background-color: #f5222d !important;
}
// 更改 输入框 校验失败的颜色
.col--valid-error > .vxe-cell > .ant-input,
.col--valid-error > .vxe-cell > .ant-select .ant-input,
.col--valid-error > .vxe-cell > .ant-select .ant-select-selection,
.col--valid-error > .vxe-cell > .ant-input-number,
.col--valid-error > .vxe-cell > .ant-cascader-picker .ant-cascader-input,
.col--valid-error > .vxe-cell > .ant-calendar-picker .ant-calendar-picker-input,
.col--valid-error > .vxe-tree-cell > .ant-input,
.col--valid-error > .vxe-tree-cell > .ant-select .ant-input,
.col--valid-error > .vxe-tree-cell > .ant-select .ant-select-selection,
.col--valid-error > .vxe-tree-cell > .ant-input-number,
.col--valid-error > .vxe-tree-cell > .ant-cascader-picker .ant-cascader-input,
.col--valid-error > .vxe-tree-cell > .ant-calendar-picker .ant-calendar-picker-input {
border-color: #f5222d !important;
}
// 拖拽排序列样式
.vxe-table .col--row-drag-sort .vxe-cell {
height: 100%;
}
@@ -1,46 +0,0 @@
.j-vxe-reload-effect-box {
&,
.j-vxe-reload-effect-span {
display: inline;
height: 100%;
position: relative;
}
.j-vxe-reload-effect-span {
&.layer-top {
display: inline-block;
width: 100%;
position: absolute;
z-index: 2;
background-color: white;
transform-origin: 0 0;
animation: reload-effect 1.5s forwards;
}
&.layer-bottom {
z-index: 1;
}
}
// 定义动画
@keyframes reload-effect {
0% {
opacity: 1;
transform: rotateX(0);
}
10% {
opacity: 1;
}
90% {
opacity: 0;
}
100% {
opacity: 0;
transform: rotateX(180deg);
}
}
}
@@ -1,332 +0,0 @@
.j-vxe-table-box {
@height: 24px;
@lineHeight: 1.5;
@spacing: 4px;
@fontSize: 14px;
@borderRadius: 2px;
&.size--tiny {
.vxe-table--header .vxe-cell--checkbox {
position: relative;
top: 2px;
right: 1px;
}
.vxe-table--body .vxe-cell--checkbox {
line-height: 2;
}
.vxe-cell {
padding: 0 5px;
font-size: @fontSize;
line-height: @lineHeight;
}
.vxe-table .vxe-header--column .vxe-cell {
font-size: 12px;
}
.vxe-body--column.col--actived {
padding: 0;
.vxe-cell {
padding: 0;
}
}
// ant输入框
.ant-input,
// ant下拉框
.ant-select-selection {
padding: 2px @spacing;
height: @height;
font-size: @fontSize;
border-radius: @borderRadius;
line-height: @lineHeight;
}
// 输入框图标对齐
.ant-input-affix-wrapper {
& .ant-input-prefix {
left: 4px;
}
& .ant-input:not(:first-child) {
padding-left: 20px;
}
}
// 按钮 addon
.ant-input-group-addon {
border-color: transparent;
border-radius: @borderRadius;
}
// ant下拉多选框
.ant-select-selection--multiple {
min-height: @height;
& .ant-select-selection__rendered > ul > li {
height: calc(@height - 6px);
font-size: calc(@fontSize - 2px);
margin-top: 0;
line-height: @lineHeight;
padding: 0 18px 0 4px;
}
& .ant-select-selection__clear,
& .ant-select-arrow {
top: 12px;
}
}
// ant按钮
.ant-upload {
width: 100%;
.ant-btn {
width: 100%;
height: @height;
padding: 0 8px;
font-size: @fontSize;
border-color: transparent;
background-color: transparent;
border-radius: @borderRadius;
&:hover {
background-color: rgba(255, 255, 255, 0.3);
}
}
}
.ant-select-selection__rendered {
line-height: @lineHeight;
margin-left: 0;
}
// 工具栏
.j-vxe-toolbar {
margin-bottom: 4px;
.ant-form-item-label,
.ant-form-item-control {
line-height: 22px;
}
.ant-form-inline .ant-form-item {
margin-right: 4px;
}
}
}
/** 内置属性 */
.vxe-table.size--tiny {
& .vxe-table--expanded {
padding-right: 0;
}
& .vxe-body--expanded-cell {
padding: 8px;
}
}
.size--tiny .vxe-loading .vxe-loading--spinner {
width: 38px;
height: 38px
}
.vxe-table.size--tiny .vxe-body--column.col--ellipsis,
.vxe-table.size--tiny .vxe-footer--column.col--ellipsis,
.vxe-table.size--tiny .vxe-header--column.col--ellipsis,
.vxe-table.vxe-editable.size--tiny .vxe-body--column {
height: @height;
}
.vxe-table.size--tiny {
font-size: 12px
}
.vxe-table.size--tiny .vxe-table--empty-block,
.vxe-table.size--tiny .vxe-table--empty-placeholder {
min-height: @height;
}
.vxe-table.size--tiny .vxe-body--column:not(.col--ellipsis),
.vxe-table.size--tiny .vxe-footer--column:not(.col--ellipsis),
.vxe-table.size--tiny .vxe-header--column:not(.col--ellipsis) {
padding: 4px 0
}
.vxe-table.size--tiny .vxe-cell .vxe-default-input,
.vxe-table.size--tiny .vxe-cell .vxe-default-select,
.vxe-table.size--tiny .vxe-cell .vxe-default-textarea {
height: @height;
}
.vxe-table.size--tiny .vxe-cell .vxe-default-input[type=date]::-webkit-inner-spin-button {
margin-top: 1px
}
.vxe-table.size--tiny.virtual--x .col--ellipsis .vxe-cell,
.vxe-table.size--tiny.virtual--y .col--ellipsis .vxe-cell,
.vxe-table.size--tiny .vxe-body--column.col--ellipsis .vxe-cell,
.vxe-table.size--tiny .vxe-footer--column.col--ellipsis .vxe-cell,
.vxe-table.size--tiny .vxe-header--column.col--ellipsis .vxe-cell {
max-height: @height;
}
.vxe-table.size--tiny .vxe-cell--checkbox .vxe-checkbox--icon,
.vxe-table.size--tiny .vxe-cell--radio .vxe-radio--icon {
font-size: 14px
}
.vxe-table.size--tiny .vxe-table--filter-option > .vxe-checkbox--icon,
.vxe-table.size--small .vxe-table--filter-option > .vxe-checkbox--icon {
font-size: 14px
}
.vxe-modal--wrapper.size--tiny .vxe-export--panel-column-option > .vxe-checkbox--icon,
.vxe-modal--wrapper.size--small .vxe-export--panel-column-option > .vxe-checkbox--icon {
font-size: 14px
}
.vxe-grid.size--tiny {
font-size: 12px
}
.vxe-toolbar.size--tiny {
font-size: 12px;
height: 46px
}
.vxe-toolbar.size--tiny .vxe-custom--option > .vxe-checkbox--icon {
font-size: 14px
}
.vxe-pager.size--tiny {
font-size: 12px;
height: @height;
}
.vxe-checkbox.size--tiny {
font-size: 12px
}
.vxe-checkbox.size--tiny .vxe-checkbox--icon {
font-size: 14px
}
.vxe-radio-button.size--tiny .vxe-radio--label {
line-height: 26px
}
.vxe-radio.size--tiny {
font-size: 12px
}
.vxe-radio.size--tiny .vxe-radio--icon {
font-size: 14px
}
.vxe-input.size--tiny {
font-size: 12px;
height: @height;
}
.vxe-input.size--tiny .vxe-input--inner[type=date]::-webkit-inner-spin-button,
.vxe-input.size--tiny .vxe-input--inner[type=month]::-webkit-inner-spin-button,
.vxe-input.size--tiny .vxe-input--inner[type=week]::-webkit-inner-spin-button {
margin-top: 0
}
.vxe-dropdown--panel.size--tiny {
font-size: 12px
}
.vxe-textarea--autosize.size--tiny,
.vxe-textarea.size--tiny {
font-size: 12px
}
.vxe-textarea.size--tiny:not(.is--autosize) {
min-height: @height;
}
.vxe-button.size--tiny {
font-size: 12px
}
.vxe-button.size--tiny.type--button {
height: @height;
}
.vxe-button.size--tiny.type--button.is--circle {
min-width: @height;
}
.vxe-button.size--tiny.type--button.is--round {
border-radius: 14px
}
.vxe-button.size--tiny .vxe-button--icon,
.vxe-button.size--tiny .vxe-button--loading-icon {
min-width: 12px
}
.vxe-modal--wrapper.size--tiny {
font-size: 12px
}
.vxe-form.size--tiny {
font-size: 12px
}
.vxe-form.size--tiny .vxe-form--item-inner {
min-height: 30px
}
.vxe-form.size--tiny .vxe-default-input[type=reset],
.vxe-form.size--tiny .vxe-default-input[type=submit] {
line-height: 26px
}
.vxe-form.size--tiny .vxe-default-input,
.vxe-form.size--tiny .vxe-default-select {
height: @height;
}
.vxe-select--panel.size--tiny,
.vxe-select.size--tiny {
font-size: 12px
}
.vxe-select--panel.size--tiny .vxe-optgroup--title,
.vxe-select--panel.size--tiny .vxe-select-option {
height: 24px;
line-height: 24px
}
.vxe-switch.size--tiny {
font-size: 12px
}
.vxe-pulldown--panel.size--tiny,
.vxe-pulldown.size--tiny {
font-size: 12px
}
}
@@ -1,322 +0,0 @@
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { filterDictText } from '@/components/dict/JDictSelectUtil'
import { getEnhancedMixins, JVXERenderType, replaceProps } from '@/components/jero/JVxeTable/utils/cellUtils'
// noinspection JSUnusedLocalSymbols
export default {
inject: {
getParentContainer: { default: () => (node) => node.parentNode }
},
props: {
value: PropTypes.any,
row: PropTypes.object,
column: PropTypes.object,
// 组件参数
params: PropTypes.object,
// 渲染选项
renderOptions: PropTypes.object,
// 渲染类型
renderType: PropTypes.string.def('default')
},
data () {
return {
innerValue: null
}
},
computed: {
caseId () {
return this.renderOptions.caseId
},
originColumn () {
return this.column.params || {}
},
$type () {
return this.originColumn.type
},
rows () {
return this.params.data
},
fullDataLength () {
return this.params.$table.tableFullData.length
},
rowIndex () {
return this.params.rowIndex
},
columnIndex () {
return this.params.columnIndex
},
cellProps () {
const { originColumn: col, renderOptions } = this
const props = {}
// 输入占位符
props.placeholder = replaceProps(col, col.placeholder)
// 解析props
if (typeof col.props === 'object') {
Object.keys(col.props).forEach(key => {
props[key] = replaceProps(col, col.props[key])
})
}
// 判断是否是禁用的列
props.disabled = (typeof col.disabled === 'boolean' ? col.disabled : props.disabled)
// TODO 判断是否是禁用的行
// if (props['disabled'] !== true) {
// props['disabled'] = ((this.disabledRowIds || []).indexOf(row.id) !== -1)
// }
// 判断是否禁用所有组件
if (renderOptions.disabled === true) {
props.disabled = true
}
return props
}
},
watch: {
$type: {
immediate: true,
handler ($type) {
this.enhanced = getEnhancedMixins($type)
this.listeners = getListeners.call(this)
}
},
value: {
immediate: true,
handler (val) {
let value = val
// 验证值格式
const originValue = this.row[this.column.property]
const getValue = this.enhanced.getValue.call(this, originValue)
if (originValue !== getValue) {
// 值格式不正确,重新赋值
value = getValue
vModel.call(this, value)
}
this.innerValue = this.enhanced.setValue.call(this, value)
// 判断是否启用翻译
if (this.renderType === JVXERenderType.spaner && this.enhanced.translate.enabled) {
const res = this.enhanced.translate.handler.call(this, value)
// 异步翻译,目前仅【多级联动】使用
if (res instanceof Promise) {
res.then(value => {
this.innerValue = value
})
} else {
this.innerValue = res
}
}
}
}
},
created () {
},
methods: {
/** 通用处理change事件 */
handleChangeCommon (value) {
const handle = this.enhanced.getValue.call(this, value)
this.trigger('change', { value: handle })
// 触发valueChange事件
this.parentTrigger('valueChange', {
type: this.$type,
value: handle,
oldValue: this.value,
col: this.originColumn,
rowIndex: this.params.rowIndex,
columnIndex: this.params.columnIndex
})
},
/** 通用处理blur事件 */
handleBlurCommon (value) {
this.trigger('blur', { value })
},
/**
* 如果事件存在的话,就触发
* @param name 事件名
* @param event 事件参数
* @param args 其他附带参数
*/
trigger (name, event, args = []) {
const listener = this.listeners[name]
if (typeof listener === 'function') {
if (typeof event === 'object') {
event = this.packageEvent(name, event)
}
listener(event, ...args)
}
},
parentTrigger (name, event, args = []) {
args.unshift(this.packageEvent(name, event))
this.trigger('trigger', name, args)
},
packageEvent (name, event = {}) {
event.row = this.row
event.column = this.column
// online增强参数兼容
event.column.key = this.column.property
event.cellTarget = this
if (!event.type) {
event.type = name
}
if (!event.cellType) {
event.cellType = this.$type
}
// 是否校验表单,默认为true
if (typeof event.validate !== 'boolean') {
event.validate = true
}
return event
}
},
model: {
prop: 'value',
event: 'change'
},
/**
* 【自定义增强】用于实现一些增强事件
* 【注】这里只是定义接口,具体功能需要到各个组件内实现(也有部分功能实现)
* 【注】该属性不是Vue官方属性,是JVxeTable组件自定义的
* 所以方法内的 this 指向并不是当前组件,而是方法自身,
* 也就是说并不能 this 打点调实例里的任何方法
*/
enhanced: {
// 注册参数(详见:https://xuliangzhan_admin.gitee.io/vxe-table/#/table/renderer/edit
installOptions: {
// 自动聚焦的 class 类名
autofocus: ''
},
// 事件拦截器(用于兼容)
interceptor: {
// 已实现:event.clearActived
// 说明:比如点击了某个组件的弹出层面板之后,此时被激活单元格不应该被自动关闭,通过返回 false 可以阻止默认的行为。
'event.clearActived' (params, event, target) {
return true
},
// 自定义:event.clearActived.className
// 说明:比原生的多了一个参数:className,用于判断点击的元素的样式名(递归到顶层)
'event.clearActived.className' (params, event, target) {
return true
}
},
// 【功能开关】
switches: {
// 是否使用 editRender 模式(仅当前组件,并非全局)
// 如果设为true,则表头上方会出现一个可编辑的图标
editRender: true,
// false = 组件触发后可视);true = 组件一直可视
visible: false
},
// 【切面增强】切面事件处理,一般在某些方法执行后同步执行
aopEvents: {
// 单元格被激活编辑时会触发该事件
editActived () {
},
// 单元格编辑状态下被关闭时会触发该事件
editClosed () {
}
},
// 【翻译增强】可以实现例如select组件保存的value,但是span模式下需要显示成text
translate: {
// 是否启用翻译
enabled: false,
/**
* 【翻译处理方法】如果handler留空,则使用默认的翻译方法
* (this指向当前组件)
*
* @param value 需要翻译的值
* @returns{*} 返回翻译后的数据
*/
handler (value) {
// 默认翻译方法
return filterDictText(this.column.own.options, value)
}
},
/**
* 【获取值增强】组件抛出的值
* (this指向当前组件)
*
* @param value 保存到数据库里的值
* @returns{*} 返回处理后的值
*/
getValue (value) {
return value
},
/**
* 【设置值增强】设置给组件的值
* (this指向当前组件)
*
* @param value 组件触发的值
* @returns{*} 返回处理后的值
*/
setValue (value) {
return value
},
/**
* 【新增行增强】在用户点击新增时触发的事件,返回新行的默认值
*
* @param row 行数据
* @param column 列配置,.own 是用户配置的参数
* @param $table vxe 实例
* @param renderOptions 渲染选项
* @param params 可以在这里获取 $table
*
* @returns 返回新值
*/
createValue ({ row, column, $table, renderOptions, params }) {
return (column.params || {}).defaultValue
}
}
}
function getListeners () {
const listeners = Object.assign({}, (this.renderOptions.listeners || {}))
if (!listeners.change) {
listeners.change = async (event) => {
vModel.call(this, event.value)
await this.$nextTick()
// 处理 change 事件相关逻辑(例如校验)
this.params.$table.updateStatus(this.params)
}
}
return listeners
}
export function vModel (value, row, property) {
if (!row) {
row = this.row
}
if (!property) {
property = this.column.property
}
this.$set(row, property, value)
}
/** 模拟触发事件 */
export function dispatchEvent ({ cell, $event }, className, handler) {
// alwaysEdit 下不模拟触发事件,否者会导致触发两次
if (this && this.alwaysEdit) {
return
}
window.setTimeout(() => {
const element = cell ? cell.getElementsByClassName(className) : null
if (element && element.length > 0) {
if (typeof handler === 'function') {
handler(element[0])
} else {
// 模拟触发点击事件
if ($event) {
element[0].dispatchEvent($event)
}
}
}
}, 10)
}
@@ -1,264 +0,0 @@
import store from '@/store/'
import { randomUUID } from '@/utils/util'
// vxe socket
const vs = {
// 页面唯一 id,用于标识同一用户,不同页面的websocket
pageId: randomUUID(),
// webSocket 对象
ws: null,
// 一些常量
constants: {
// 消息类型
TYPE: 'type',
// 消息数据
DATA: 'data',
// 消息类型:心跳检测
TYPE_HB: 'heart_beat',
// 消息类型:通用数据传递
TYPE_CSD: 'common_send_date',
// 消息类型:更新vxe table数据
TYPE_UVT: 'update_vxe_table'
},
// 心跳检测
heartCheck: {
// 间隔时间,间隔多久发送一次心跳消息
interval: 10000,
// 心跳消息超时时间,心跳消息多久没有回复后重连
timeout: 6000,
timeoutTimer: null,
clear () {
clearTimeout(this.timeoutTimer)
return this
},
start () {
vs.sendMessage(vs.constants.TYPE_HB, '')
// 如果超过一定时间还没重置,说明后端主动断开了
this.timeoutTimer = window.setTimeout(() => {
vs.reconnect()
}, this.timeout)
return this
},
// 心跳消息返回
back () {
this.clear()
window.setTimeout(() => this.start(), this.interval)
}
},
/** 初始化 WebSocket */
initialWebSocket () {
if (this.ws === null) {
const userId = store.getters.userInfo.id
const domain = window._CONFIG.domianWebSocketURL.replace('https://', 'wss://').replace('http://', 'ws://')
const url = `${domain}/vxeSocket/${userId}/${this.pageId}`
this.ws = new WebSocket(url)
this.ws.onopen = this.on.open.bind(this)
this.ws.onerror = this.on.error.bind(this)
this.ws.onmessage = this.on.message.bind(this)
this.ws.onclose = this.on.close.bind(this)
console.log('this.ws: ', this.ws)
}
},
// 发送消息
sendMessage (type, message) {
try {
const ws = this.ws
if (ws != null && ws.readyState === ws.OPEN) {
ws.send(JSON.stringify({
type: type,
data: message
}))
}
} catch (err) {
console.warn('【VXEWebSocket】发送消息失败:(' + err.code + ')')
}
},
/** 绑定全局VXE表格 */
tableMap: new Map(),
CSDMap: new Map(),
/** 添加绑定 */
addBind (map, key, value) {
const binds = map.get(key)
if (Array.isArray(binds)) {
binds.push(value)
} else {
map.set(key, [value])
}
},
/** 移除绑定 */
removeBind (map, key, value) {
const binds = map.get(key)
if (Array.isArray(binds)) {
for (let i = 0; i < binds.length; i++) {
const bind = binds[i]
if (bind === value) {
binds.splice(i, 1)
break
}
}
if (binds.length === 0) {
map.delete(key)
}
} else {
map.delete(key)
}
},
// 呼叫绑定的表单
callBind (map, key, callback) {
const binds = map.get(key)
if (Array.isArray(binds)) {
binds.forEach(callback)
}
},
lockReconnect: false,
/** 尝试重连 */
reconnect () {
if (this.lockReconnect) return
this.lockReconnect = true
setTimeout(() => {
if (this.ws && this.ws.close) {
this.ws.close()
}
this.ws = null
console.info('【VXEWebSocket】尝试重连...')
this.initialWebSocket()
this.lockReconnect = false
}, 5000)
},
on: {
open () {
console.log('【VXEWebSocket】连接成功')
this.heartCheck.start()
},
error (e) {
console.warn('【VXEWebSocket】连接发生错误:', e)
this.reconnect()
},
message (e) {
// 解析消息
let json
try {
json = JSON.parse(e.data)
} catch (e) {
console.warn('【VXEWebSocket】收到无法解析的消息:', e.data)
return
}
const type = json[this.constants.TYPE]
const data = json[this.constants.DATA]
switch (type) {
// 心跳检测
case this.constants.TYPE_HB:
this.heartCheck.back()
break
// 通用数据传递
case this.constants.TYPE_CSD:
this.callBind(this.CSDMap, data.key, (fn) => fn.apply(this, data.args))
break
// 更新form数据
case this.constants.TYPE_UVT:
this.callBind(this.tableMap, data.socketKey, (vm) => this.onVM.onUpdateTable.apply(vm, data.args))
break
default:
console.warn('【VXEWebSocket】收到不识别的消息类型:' + type)
break
}
},
close (e) {
console.log('【VXEWebSocket】连接被关闭:', e)
this.reconnect()
}
},
onVM: {
/** 收到更新表格的消息 */
onUpdateTable (row, caseId) {
// 判断是不是自己发的消息
if (this.caseId !== caseId) {
const tableRow = this.getIfRowById(row.id).row
// 局部保更新数据
if (tableRow) {
// 特殊处理拖轮状态
if (row.tug_status && tableRow.tug_status) {
row.tug_status = Object.assign({}, tableRow.tug_status, row.tug_status)
}
// 判断是否启用重载特效
if (this.reloadEffect) {
this.$set(this.reloadEffectRowKeysMap, row.id, true)
}
Object.keys(row).forEach(key => {
if (key !== 'id') {
this.$set(tableRow, key, row[key])
}
})
this.$refs.vxe.reloadRow(tableRow)
}
}
}
}
}
export default {
props: {
// 是否开启使用 webSocket 无痕刷新
socketReload: {
type: Boolean,
default: false
},
socketKey: {
type: String,
default: 'vxe-default'
}
},
data () {
return {}
},
mounted () {
if (this.socketReload) {
vs.initialWebSocket()
vs.addBind(vs.tableMap, this.socketKey, this)
}
},
methods: {
/** 发送socket消息更新行 */
socketSendUpdateRow (row) {
vs.sendMessage(vs.constants.TYPE_UVT, {
socketKey: this.socketKey,
args: [row, this.caseId]
})
}
},
beforeDestroy () {
vs.removeBind(vs.tableMap, this.socketKey, this)
}
}
/**
* 添加WebSocket通用数据传递绑定,相同的key可以添加多个方法绑定
* @param key 唯一key
* @param fn 当消息来的时候触发的回调方法
*/
export function addBindSocketCSD (key, fn) {
if (typeof fn === 'function') {
vs.addBind(vs.CSDMap, key, fn)
}
}
/**
* 移除WebSocket通用数据传递绑定
* @param key 唯一key
* @param fn 要移除的方法,必须和添加时的方法内存层面上保持一致才可以正确移除
*/
export function removeBindSocketCSD (key, fn) {
if (typeof fn === 'function') {
vs.removeBind(vs.CSDMap, key, fn)
}
}
@@ -1,130 +0,0 @@
import { AllCells, JVXETypes } from '@/components/jero/JVxeTable'
import JVxeCellMixins from '../mixins/JVxeCellMixins'
export const JVXERenderType = {
editer: 'editer',
spaner: 'spaner',
default: 'default'
}
/** 安装所有vxe组件 */
export function installAllCell (VXETable) {
// 遍历所有组件批量注册
Object.keys(AllCells).forEach(type => installOneCell(VXETable, type))
}
/** 安装单个vxe组件 */
export function installOneCell (VXETable, type) {
const switches = getEnhancedMixins(type, 'switches')
if (switches.editRender === false) {
installCellRender(VXETable, type, AllCells[type])
} else {
installEditRender(VXETable, type, AllCells[type])
}
}
/** 注册可编辑组件 */
export function installEditRender (VXETable, type, comp, spanComp) {
// 获取当前组件的增强
const enhanced = getEnhancedMixins(type)
// span 组件
if (!spanComp && AllCells[type + ':span']) {
spanComp = AllCells[type + ':span']
} else {
spanComp = AllCells[JVXETypes.normal]
}
// 如果是常显的cell,显示模板和编辑模板都用编辑模板
const cellRender = enhanced.switches.visible ? createRender(comp, enhanced, JVXERenderType.editer) : createRender(spanComp, enhanced, JVXERenderType.spaner)
// 添加渲染
VXETable.renderer.add(JVXETypes._prefix + type, {
// 可编辑模板
renderEdit: createRender(comp, enhanced, JVXERenderType.editer),
// 显示模板
renderCell: cellRender,
// 增强注册
...enhanced.installOptions
})
}
/** 注册普通组件 */
export function installCellRender (VXETable, type, comp = AllCells[JVXETypes.normal]) {
// 获取当前组件的增强
const enhanced = getEnhancedMixins(type)
VXETable.renderer.add(JVXETypes._prefix + type, {
// 默认显示模板
renderDefault: createRender(comp, enhanced, JVXERenderType.default),
// 增强注册
...enhanced.installOptions
})
}
export function createRender (comp, enhanced, renderType) {
return function (h, renderOptions, params) {
return [h(comp, {
props: {
value: params.row[params.column.property],
row: params.row,
column: params.column,
params: params,
renderOptions: renderOptions,
renderType: renderType
}
})]
}
}
// 已混入的组件增强
const AllCellsMixins = new Map()
/** 获取某个组件的增强 */
export function getEnhanced (type) {
const cell = AllCells[type]
if (cell && cell.enhanced) {
return cell.enhanced
}
return null
}
/**
* 获取某个组件的增强(混入默认值)
*
* @param type JVXETypes
* @param name 可空,增强名称,留空返回所有增强
*/
export function getEnhancedMixins (type, name) {
const getByName = (e) => name ? e[name] : e
if (AllCellsMixins.has(type)) {
return getByName(AllCellsMixins.get(type))
}
const defEnhanced = JVxeCellMixins.enhanced
const enhanced = getEnhanced(type)
if (enhanced) {
Object.keys(defEnhanced).forEach(key => {
const def = defEnhanced[key]
if (Object.prototype.hasOwnProperty.call(enhanced, key)) {
// 方法如果存在就不覆盖
if (typeof def !== 'function' && typeof def !== 'string') {
enhanced[key] = Object.assign({}, def, enhanced[key])
}
} else {
enhanced[key] = def
}
})
AllCellsMixins.set(type, enhanced)
return getByName(enhanced)
}
AllCellsMixins.set(type, defEnhanced)
return getByName(defEnhanced)
}
/** 辅助方法:替换${...}变量 */
export function replaceProps (col, value) {
if (value && typeof value === 'string') {
let text = value
text = text.replace(/\${title}/g, col.title)
text = text.replace(/\${key}/g, col.key)
text = text.replace(/\${defaultValue}/g, col.defaultValue)
return text
}
return value
}
@@ -1,220 +0,0 @@
import { getVmParentByName } from '@/utils/util'
import { JVXETypes } from '@comp/jero/JVxeTable'
export const VALIDATE_FAILED = Symbol('')
/**
* 获取指定的 $refs 对象
* 有时候可能会遇到组件未挂载到页面中的情况,导致无法获取 $refs 中的某个对象
* 这个方法可以等待挂载完成之后再返回 $refs 的对象,避免报错
* @author sunjianlei
**/
export function getRefPromise (vm, name) {
return new Promise((resolve) => {
(function next () {
const ref = vm.$refs[name]
if (ref) {
resolve(ref)
} else {
setTimeout(() => {
next()
}, 10)
}
})()
})
}
/** 获取某一数字输入框列中的最大的值 */
export function getInputNumberMaxValue (col, rowsValues) {
let maxNum = 0
Object.values(rowsValues).forEach((rowValue, index) => {
const val = rowValue[col.key]; let num
try {
num = Number.parseFloat(val)
} catch {
num = 0
}
// 把首次循环的结果当成最大值
if (index === 0) {
maxNum = num
} else {
maxNum = (num > maxNum) ? num : maxNum
}
})
return maxNum
}
/**
*
* 根据 tagName 获取父级节点
*
* @param dom 一级dom节点
* @param tagName 标签名,不区分大小写
* @return {HTMLElement}
*/
export function getParentNodeByTagName (dom, tagName = 'body') {
if (tagName === 'body') {
return document.body
}
if (dom.parentNode) {
if (dom.parentNode.tagName.toLowerCase() === tagName.trim().toLowerCase()) {
return dom.parentNode
} else {
return getParentNodeByTagName(dom.parentNode, tagName)
}
} else {
return null
}
}
/**
* vxe columns 封装成高级查询可识别的选项
* @param columns
* @param handler 单独处理方法
*/
export function vxePackageToSuperQuery (columns, handler) {
if (Array.isArray(columns)) {
// 高级查询所需要的参数
const fieldList = []
// 遍历列
for (let i = 0; i < columns.length; i++) {
const col = columns[i]
if (col.type === JVXETypes.rowCheckbox ||
col.type === JVXETypes.rowRadio ||
col.type === JVXETypes.rowExpand ||
col.type === JVXETypes.rowNumber
) {
continue
}
const field = {
type: 'string',
value: col.key,
text: col.title,
dictCode: col.dictCode || col.dict
}
if (col.type === JVXETypes.date || col.type === JVXETypes.datetime) {
field.type = col.type
field.format = col.format
}
if (col.type === JVXETypes.inputNumber) {
field.type = 'int'
}
if (Array.isArray(col.options)) {
field.options = col.options
}
if (typeof handler === 'function') {
Object.assign(field, handler(col))
}
fieldList.push(field)
}
return fieldList
} else {
console.error('columns必须是一个数组')
}
return null
}
/**
* 一次性验证主表单和所有的次表单
* @param form 主表单 form 对象
* @param cases 接收一个数组,每项都是一个JVxeTable实例
* @param autoJumpTab
* @returns {Promise<any>}
* @author sunjianlei
*/
export async function validateFormAndTables (form, cases, autoJumpTab) {
if (!(form && typeof form.validateFields === 'function')) {
throw new Error(`form 参数需要的是一个form对象,而传入的却是${typeof form}`)
}
let dataMap = {}
const values = await new Promise((resolve, reject) => {
// 验证主表表单
form.validateFields((err, values) => {
err ? reject({ error: VALIDATE_FAILED, originError: err }) : resolve(values)
})
})
Object.assign(dataMap, { formValue: values })
// 验证所有子表的表单
const subData = await validateTables(cases, autoJumpTab)
// 合并最终数据
dataMap = Object.assign(dataMap, { tablesValue: subData })
return dataMap
}
/**
* 一次性验证主表单和所有的次表单
* @param form 主表单 form 对象
* @param formData
* @param cases 接收一个数组,每项都是一个JVxeTable实例
* @param autoJumpTab
* @returns {Promise<any>}
* @author sunjianlei
*/
export async function validateFormModelAndTables (form, formData, cases, autoJumpTab) {
if (!(form && typeof form.validate === 'function')) {
throw new Error(`form 参数需要的是一个form对象,而传入的却是${typeof form}`)
}
let dataMap = {}
const values = await new Promise((resolve, reject) => {
// 验证主表表单
form.validate((valid) => {
valid ? resolve(formData) : reject({ error: VALIDATE_FAILED, originError: valid })
})
})
Object.assign(dataMap, { formValue: values })
// 验证所有子表的表单
const subData = await validateTables(cases, autoJumpTab)
// 合并最终数据
dataMap = Object.assign(dataMap, { tablesValue: subData })
return dataMap
}
/**
* 验证并获取一个或多个表格的所有值
*
* @param cases 接收一个数组,每项都是一个JVxeTable实例
* @param autoJumpTab 校验失败后,是否自动跳转tab选项
*/
export function validateTables (cases, autoJumpTab = true) {
if (!Array.isArray(cases)) {
throw new Error(`'validateTables'函数的'cases'参数需要的是一个数组,而传入的却是${typeof cases}`)
}
return new Promise((resolve, reject) => {
const tablesData = []
let index = 0
if (!cases || cases.length === 0) {
resolve()
}
(function next () {
const vm = cases[index]
vm.validateTable().then(errMap => {
// 校验通过
if (!errMap) {
tablesData[index] = vm.getAll()
// 判断校验是否全部完成,完成返回成功,否则继续进行下一步校验
if (++index === cases.length) {
resolve(tablesData)
} else {
(
next()
)
}
} else {
// 尝试获取tabKey,如果在ATab组件内即可获取
let paneKey
const tabPane = getVmParentByName(vm, 'ATabPane')
if (tabPane) {
paneKey = tabPane.$vnode.key
// 自动跳转到该表格
if (autoJumpTab) {
const tabs = getVmParentByName(tabPane, 'Tabs')
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey)
}
}
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
reject({ error: VALIDATE_FAILED, index, paneKey, errMap })
}
})
})()
})
}
+1 -1
View File
@@ -119,7 +119,7 @@ export default {
mounted () {
this.loadData()
// this.timerFun();
this.initWebSocket()
// this.initWebSocket()
// this.heartCheckFun();
},
destroyed: function () { // 离开页面生命周期函数
@@ -23,13 +23,9 @@
</template>
<script>
import JModal from '../jero/JModal'
export default {
name: 'SysAnnouncementModal',
components: {
JModal
},
data () {
return {
title: '通知消息',