JeecgBoot2.3 里程碑版本,支持微服务和单体自由切换、提供新行编辑表格JVXETable
This commit is contained in:
parent
2d59d99e2a
commit
0a315b9abb
12
README.md
12
README.md
|
@ -7,7 +7,7 @@
|
|||
JEECG BOOT 低代码开发平台(前后端分离版本)
|
||||
===============
|
||||
|
||||
当前最新版本: 2.2.1(发布日期:2020-07-13)
|
||||
当前最新版本: 2.3(发布日期:2020-09-14)
|
||||
|
||||
|
||||
[](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
|
||||
|
@ -56,7 +56,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
交流互动
|
||||
-----------------------------------
|
||||
|
||||
- QQ交流群 : ②769925425、①284271917(满)
|
||||
- QQ交流群 : ②769925425、③816531124、①284271917(满)
|
||||
|
||||
- 反馈问题: [反馈问题,请按格式发Issues](https://github.com/zhangdaiscott/jeecg-boot/issues/new)
|
||||
|
||||
|
@ -167,12 +167,13 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
│ ├─权限设置(支持按钮权限、数据权限)
|
||||
│ ├─表单权限(控制字段禁用、隐藏)
|
||||
│ ├─部门管理
|
||||
│ ├─我的部门(二级管理员)
|
||||
│ └─字典管理
|
||||
│ └─树分类字典
|
||||
│ └─分类字典
|
||||
│ └─系统公告
|
||||
│ └─我的组织机构
|
||||
│ └─职务管理
|
||||
│ └─通讯录
|
||||
│ └─多租户管理
|
||||
├─消息中心
|
||||
│ ├─消息管理
|
||||
│ ├─模板管理
|
||||
|
@ -185,6 +186,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
│ ├─Excel导入导出工具集成(支持单表,一对多 导入导出)
|
||||
│ ├─平台移动自适应支持
|
||||
├─系统监控
|
||||
│ ├─Gateway路由网关
|
||||
│ ├─性能扫描监控
|
||||
│ │ ├─监控 Redis
|
||||
│ │ ├─Tomcat
|
||||
|
@ -216,6 +218,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
│─常用示例
|
||||
│ ├─自定义组件
|
||||
│ ├─对象存储(对接阿里云)
|
||||
│ ├─JVXETable示例(各种复杂ERP布局示例)
|
||||
│ ├─单表模型例子
|
||||
│ └─一对多模型例子
|
||||
│ └─打印例子
|
||||
|
@ -267,6 +270,7 @@ Jeecg-Boot低代码开发平台,可以应用在任何J2EE项目的开发中,
|
|||
│ ├─Online在线表单 - 功能已开放
|
||||
│ ├─在线代码生成器 - 功能已开放
|
||||
│ ├─Online在线报表 - 功能已开放
|
||||
│ ├─多数据源管理
|
||||
│ ├─Online在线图表
|
||||
│ ├─Online图表模板配置
|
||||
│ ├─高级表单设计器
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
Ant Design Jeecg Vue
|
||||
====
|
||||
|
||||
当前最新版本: 2.2.1(发布日期:20200713)
|
||||
当前最新版本: 2.3.0(发布日期:20200914)
|
||||
|
||||
Overview
|
||||
----
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue-antd-jeecg",
|
||||
"version": "2.2.1",
|
||||
"version": "2.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"pre": "cnpm install || yarn --registry https://registry.npm.taobao.org || npm install --registry https://registry.npm.taobao.org ",
|
||||
|
@ -10,8 +10,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^1.6.3",
|
||||
"@jeecg/antd-online-mini": "2.3.0",
|
||||
"@antv/data-set": "^0.11.4",
|
||||
"@jeecg/antd-online-mini": "2.2.12",
|
||||
"viser-vue": "^2.4.8",
|
||||
"axios": "^0.18.0",
|
||||
"dayjs": "^1.8.0",
|
||||
|
@ -39,7 +39,11 @@
|
|||
"@toast-ui/editor": "^2.1.2",
|
||||
"vue-area-linkage": "^5.1.0",
|
||||
"area-data": "^5.0.6",
|
||||
"jsoneditor": "^9.0.0"
|
||||
"jsoneditor": "^9.0.0",
|
||||
"dom-align": "1.12.0",
|
||||
"xe-utils": "2.4.8",
|
||||
"vxe-table": "2.9.13",
|
||||
"vxe-table-plugin-antd": "1.8.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/polyfill": "^7.2.5",
|
||||
|
@ -48,13 +52,14 @@
|
|||
"@vue/cli-service": "^3.3.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"babel-eslint": "7.2.3",
|
||||
"compression-webpack-plugin": "^3.1.0",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.1.0",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.1.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"compression-webpack-plugin": "^3.1.0",
|
||||
"babel-plugin-import": "^1.13.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
|
|
@ -7687,6 +7687,9 @@ font.weak {
|
|||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.ant-menu-submenu-selected {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
// begin -------- JAreaLinkage 三级联动样式 --------------
|
||||
.cascader-menu-list .cascader-menu-option.hover,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<title>Jeecg-Boot 企业级快速开发平台</title>
|
||||
<link rel="icon" href="<%= BASE_URL %>logo.png">
|
||||
<script src="/cdn/babel-polyfill/polyfill_7_2_5.js"></script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
|
|
|
@ -2,9 +2,17 @@ import { getAction, deleteAction, putAction, postAction, httpAction } from '@/ap
|
|||
import Vue from 'vue'
|
||||
import {UI_CACHE_DB_DICT_DATA } from "@/store/mutation-types"
|
||||
|
||||
////根路径
|
||||
// const doMian = "/jeecg-boot/";
|
||||
////图片预览请求地址
|
||||
// const imgView = "http://localhost:8080/jeecg-boot/sys/common/view/";
|
||||
|
||||
//角色管理
|
||||
const addRole = (params)=>postAction("/sys/role/add",params);
|
||||
const editRole = (params)=>putAction("/sys/role/edit",params);
|
||||
// const getRoleList = (params)=>getAction("/sys/role/list",params);
|
||||
// const deleteRole = (params)=>deleteAction("/sys/role/delete",params);
|
||||
// const deleteRoleList = (params)=>deleteAction("/sys/role/deleteBatch",params);
|
||||
const checkRoleCode = (params)=>getAction("/sys/role/checkRoleCode",params);
|
||||
const queryall = (params)=>getAction("/sys/role/queryall",params);
|
||||
|
||||
|
@ -13,6 +21,8 @@ const addUser = (params)=>postAction("/sys/user/add",params);
|
|||
const editUser = (params)=>putAction("/sys/user/edit",params);
|
||||
const queryUserRole = (params)=>getAction("/sys/user/queryUserRole",params);
|
||||
const getUserList = (params)=>getAction("/sys/user/list",params);
|
||||
// const deleteUser = (params)=>deleteAction("/sys/user/delete",params);
|
||||
// const deleteUserList = (params)=>deleteAction("/sys/user/deleteBatch",params);
|
||||
const frozenBatch = (params)=>putAction("/sys/user/frozenBatch",params);
|
||||
//验证用户是否存在
|
||||
const checkOnlyUser = (params)=>getAction("/sys/user/checkOnlyUser",params);
|
||||
|
@ -27,12 +37,16 @@ const getPermissionList = (params)=>getAction("/sys/permission/list",params);
|
|||
const getSystemMenuList = (params)=>getAction("/sys/permission/getSystemMenuList",params);
|
||||
const getSystemSubmenu = (params)=>getAction("/sys/permission/getSystemSubmenu",params);
|
||||
const getSystemSubmenuBatch = (params) => getAction('/sys/permission/getSystemSubmenuBatch', params)
|
||||
/*update_end author:wuxianquan date:20190908 for:添加查询一级菜单和子菜单查询api */
|
||||
|
||||
// const deletePermission = (params)=>deleteAction("/sys/permission/delete",params);
|
||||
// const deletePermissionList = (params)=>deleteAction("/sys/permission/deleteBatch",params);
|
||||
const queryTreeList = (params)=>getAction("/sys/permission/queryTreeList",params);
|
||||
const queryTreeListForRole = (params)=>getAction("/sys/role/queryTreeList",params);
|
||||
const queryListAsync = (params)=>getAction("/sys/permission/queryListAsync",params);
|
||||
const queryRolePermission = (params)=>getAction("/sys/permission/queryRolePermission",params);
|
||||
const saveRolePermission = (params)=>postAction("/sys/permission/saveRolePermission",params);
|
||||
//const queryPermissionsByUser = (params)=>getAction("/sys/permission/queryByUser",params);
|
||||
const queryPermissionsByUser = (params)=>getAction("/sys/permission/getUserPermissionByToken",params);
|
||||
const loadAllRoleIds = (params)=>getAction("/sys/permission/loadAllRoleIds",params);
|
||||
const getPermissionRuleList = (params)=>getAction("/sys/permission/getPermRuleListByPermId",params);
|
||||
|
@ -61,9 +75,14 @@ const deleteLogList = (params)=>deleteAction("/sys/log/deleteBatch",params);
|
|||
//数据字典
|
||||
const addDict = (params)=>postAction("/sys/dict/add",params);
|
||||
const editDict = (params)=>putAction("/sys/dict/edit",params);
|
||||
//const getDictList = (params)=>getAction("/sys/dict/list",params);
|
||||
const treeList = (params)=>getAction("/sys/dict/treeList",params);
|
||||
// const delDict = (params)=>deleteAction("/sys/dict/delete",params);
|
||||
//const getDictItemList = (params)=>getAction("/sys/dictItem/list",params);
|
||||
const addDictItem = (params)=>postAction("/sys/dictItem/add",params);
|
||||
const editDictItem = (params)=>putAction("/sys/dictItem/edit",params);
|
||||
//const delDictItem = (params)=>deleteAction("/sys/dictItem/delete",params);
|
||||
//const delDictItemList = (params)=>deleteAction("/sys/dictItem/deleteBatch",params);
|
||||
|
||||
//字典标签专用(通过code获取字典数组)
|
||||
export const ajaxGetDictItems = (code, params)=>getAction(`/sys/dict/getDictItems/${code}`,params);
|
||||
|
@ -82,10 +101,14 @@ const doReovkeData = (params)=>getAction("/sys/annountCement/doReovkeData",param
|
|||
//获取系统访问量
|
||||
const getLoginfo = (params)=>getAction("/sys/loginfo",params);
|
||||
const getVisitInfo = (params)=>getAction("/sys/visitInfo",params);
|
||||
//数据日志访问
|
||||
// const getDataLogList = (params)=>getAction("/sys/dataLog/list",params);
|
||||
|
||||
// 根据部门主键查询用户信息
|
||||
const queryUserByDepId = (params)=>getAction("/sys/user/queryUserByDepId",params);
|
||||
|
||||
// 查询用户角色表里的所有信息
|
||||
// const queryUserRoleMap = (params)=>getAction("/sys/user/queryUserRoleMap",params);
|
||||
// 重复校验
|
||||
const duplicateCheck = (params)=>getAction("/sys/duplicate/check",params);
|
||||
// 加载分类字典
|
||||
|
@ -103,6 +126,8 @@ export const transitRESTful = {
|
|||
}
|
||||
|
||||
export {
|
||||
// imgView,
|
||||
// doMian,
|
||||
addRole,
|
||||
editRole,
|
||||
checkRoleCode,
|
||||
|
|
|
@ -167,11 +167,15 @@ export function uploadAction(url,parameter){
|
|||
*/
|
||||
export function getFileAccessHttpUrl(avatar,subStr) {
|
||||
if(!subStr) subStr = 'http'
|
||||
if(avatar && avatar.startsWith(subStr)){
|
||||
return avatar;
|
||||
}else{
|
||||
if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
|
||||
return window._CONFIG['staticDomainURL'] + "/" + avatar;
|
||||
try {
|
||||
if(avatar && avatar.startsWith(subStr)){
|
||||
return avatar;
|
||||
}else{
|
||||
if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
|
||||
return window._CONFIG['staticDomainURL'] + "/" + avatar;
|
||||
}
|
||||
}
|
||||
}catch(err){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
.cm-s-idea span.cm-meta{color:olive}.cm-s-idea span.cm-number{color:#00f}.cm-s-idea span.cm-keyword{line-height:1em;font-weight:700;color:navy}.cm-s-idea span.cm-atom{font-weight:700;color:navy}.cm-s-idea span.cm-def{color:#000}.cm-s-idea span.cm-variable{color:#000}.cm-s-idea span.cm-variable-2{color:#000}.cm-s-idea span.cm-type,.cm-s-idea span.cm-variable-3{color:#000}.cm-s-idea span.cm-property{color:#000}.cm-s-idea span.cm-operator{color:#000}.cm-s-idea span.cm-comment{color:grey}.cm-s-idea span.cm-string{color:green}.cm-s-idea span.cm-string-2{color:green}.cm-s-idea span.cm-qualifier{color:#555}.cm-s-idea span.cm-error{color:red}.cm-s-idea span.cm-attribute{color:#00f}.cm-s-idea span.cm-tag{color:navy}.cm-s-idea span.cm-link{color:#00f}.cm-s-idea .CodeMirror-activeline-background{background:#fffae3}.cm-s-idea span.cm-builtin{color:#30a}.cm-s-idea span.cm-bracket{color:#cc7}.cm-s-idea{font-family:Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,serif}.cm-s-idea .CodeMirror-matchingbracket{outline:1px solid grey;color:#000!important}.CodeMirror-hints.idea{font-family:Menlo,Monaco,Consolas,'Courier New',monospace;color:#616569;background-color:#ebf3fd!important}.CodeMirror-hints.idea .CodeMirror-hint-active{background-color:#a2b8c9!important;color:#5c6065!important}
|
|
@ -0,0 +1,205 @@
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" 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.name">
|
||||
<a-icon type="paper-clip" style="color:red;"/>
|
||||
<span style="color:red;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"/> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><a-icon type="delete"/> 删除</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleMoreOperation">
|
||||
<span><a-icon type="bars"/> 更多</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</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"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileAccessHttpUrl } from '@api/manage'
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import JFilePop from '@/components/jeecg/minipop/JFilePop'
|
||||
|
||||
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
|
||||
|
||||
export default {
|
||||
name: 'JVxeFileCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {JFilePop},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerFile: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders() {
|
||||
let {originColumn: col} = this
|
||||
let 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() {
|
||||
let length = 5
|
||||
let 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() {
|
||||
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) {
|
||||
let {originColumn: col} = this
|
||||
let {file} = info
|
||||
let 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') {
|
||||
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>
|
|
@ -0,0 +1,230 @@
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" 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>
|
||||
<template v-else>
|
||||
<a-icon type="exclamation-circle" style="color: red;" @click="handleClickShowImageError"/>
|
||||
</template>
|
||||
<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="上传失败">
|
||||
<a-icon type="exclamation-circle" style="color:red;"/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<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"/> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><a-icon type="delete"/> 删除</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item @click="handleMoreOperation">
|
||||
<span><a-icon type="bars"/> 更多</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</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"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getFileAccessHttpUrl } from '@api/manage'
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import JFilePop from '@/components/jeecg/minipop/JFilePop'
|
||||
|
||||
import JVxeUploadCell from '@/components/jeecg/JVxeTable/components/cells/JVxeUploadCell'
|
||||
|
||||
export default {
|
||||
name: 'JVxeImageCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {JFilePop},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerFile: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
/** upload headers */
|
||||
uploadHeaders() {
|
||||
let {originColumn: col} = this
|
||||
let 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']) {
|
||||
let 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() {
|
||||
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() {
|
||||
let file = this.innerFile || null
|
||||
if (file && file['message']) {
|
||||
this.$error({title: '上传出错', content: '错误信息:' + file['message'], maskClosable: true})
|
||||
}
|
||||
},
|
||||
|
||||
handleChangeUpload(info) {
|
||||
let {originColumn: col} = this
|
||||
let {file} = info
|
||||
let 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') {
|
||||
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>
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<j-popup
|
||||
v-bind="popupProps"
|
||||
@input="handlePopupInput"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins, { vModel, dispatchEvent } from '@/components/jeecg/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,
|
||||
}
|
||||
},
|
||||
},
|
||||
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 => {
|
||||
let 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(event, 'ant-input'),
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,60 @@
|
|||
<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/jeecg/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>
|
|
@ -0,0 +1,260 @@
|
|||
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/jeecg/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() {
|
||||
let 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 {
|
||||
let {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) {
|
||||
let arr = this.dict.split(',')
|
||||
if (arr[0].indexOf('where') > 0) {
|
||||
let 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) {
|
||||
//优先从缓存中读取字典配置
|
||||
let cache = getDictItemsFromCache(this.dict)
|
||||
if (cache) {
|
||||
this.innerOptions = cache
|
||||
return
|
||||
}
|
||||
}
|
||||
let {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
|
||||
}
|
||||
let {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() {
|
||||
let 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(event, 'ant-select'),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { installCell, JVXETypes } from '@/components/jeecg/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/jeecg/JVxeTable/components/cells/JVxeSelectCell'
|
||||
import JVxeTextareaCell from '@comp/jeecg/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)
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div :style="{ padding: '0 0 32px 32px' }">
|
||||
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
|
||||
<v-chart :data="data" :height="height" :force-fit="true" :onClick="handleClick">
|
||||
<v-chart :data="data" :height="height" :force-fit="true" :scale="scale" :onClick="handleClick">
|
||||
<v-tooltip/>
|
||||
<v-axis/>
|
||||
<v-legend/>
|
||||
|
@ -78,6 +78,14 @@
|
|||
}
|
||||
})
|
||||
return rows
|
||||
},
|
||||
scale() {
|
||||
return [
|
||||
{
|
||||
type: 'cat',
|
||||
dataKey: 'x'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@
|
|||
data() {
|
||||
return {
|
||||
scale: [{
|
||||
type: 'cat',
|
||||
dataKey: 'x',
|
||||
min: 0,
|
||||
max: 1
|
||||
|
|
|
@ -90,6 +90,14 @@
|
|||
handler(){
|
||||
this.initDictData()
|
||||
}
|
||||
},
|
||||
'dictOptions':{
|
||||
deep: true,
|
||||
handler(val){
|
||||
if(val && val.length>0){
|
||||
this.options = [...val]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
|
|
|
@ -0,0 +1,282 @@
|
|||
<template>
|
||||
<div v-bind="fullScreenParentProps">
|
||||
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
|
||||
|
||||
<div class="code-editor-cust full-screen-child">
|
||||
<a-textarea auto-size v-model="textareaValue" :placeholder="placeholderShow" @change="handleChange" :style="{'max-height': maxHeight+'px','min-height': minHeight+'px'}"></a-textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="text/ecmascript-6">
|
||||
export default {
|
||||
name: 'JCodeEditor',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 全屏以后的z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
// 是否自适应高度,可以传String或Boolean
|
||||
// 传 String 类型只能写"!ie" ,
|
||||
// 填写这个字符串,代表其他浏览器自适应高度
|
||||
// 唯独IE下不自适应高度,因为IE下不支持min、max-height样式
|
||||
// 如果填写的不是"!ie"就视为true
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 不自适应高度的情况下生效的固定高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '240px'
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
minHeight:{
|
||||
type:Number,
|
||||
default: 100,
|
||||
required:false
|
||||
},
|
||||
maxHeight:{
|
||||
type:Number,
|
||||
default: 320,
|
||||
required:false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
textareaValue: '',
|
||||
// 内部真实的内容
|
||||
code: '',
|
||||
iconType: 'fullscreen',
|
||||
hasCode:false,
|
||||
// 默认的语法类型
|
||||
mode: 'javascript',
|
||||
// 编辑器实例
|
||||
coder: null,
|
||||
// 默认配置
|
||||
options: {
|
||||
// 缩进格式
|
||||
tabSize: 2,
|
||||
// 主题,对应主题库 JS 需要提前引入
|
||||
theme: 'panda-syntax',
|
||||
line: true,
|
||||
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
|
||||
hintOptions: {
|
||||
tables: {
|
||||
users: ['name', 'score', 'birthDate'],
|
||||
countries: ['name', 'population', 'size']
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// code 编辑器 是否全屏
|
||||
fullCoder: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fullCoder:{
|
||||
handler(value) {
|
||||
if(value){
|
||||
this.iconType="fullscreen-exit"
|
||||
}else{
|
||||
this.iconType="fullscreen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholderShow() {
|
||||
if (this.placeholder == null) {
|
||||
return `请在此输入代码`
|
||||
} else {
|
||||
return this.placeholder
|
||||
}
|
||||
},
|
||||
isAutoHeight() {
|
||||
let {autoHeight} = this
|
||||
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
|
||||
autoHeight = !(isIE() || isIE11())
|
||||
} else {
|
||||
autoHeight = true
|
||||
}
|
||||
return autoHeight
|
||||
},
|
||||
fullScreenParentProps() {
|
||||
let props = {
|
||||
class: {
|
||||
'full-screen-parent': true,
|
||||
'full-screen': this.fullCoder,
|
||||
'auto-height': this.isAutoHeight
|
||||
},
|
||||
style: {}
|
||||
}
|
||||
if (this.fullCoder) {
|
||||
props.style['z-index'] = this.zIndex
|
||||
}
|
||||
if (!this.isAutoHeight) {
|
||||
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// 初始化
|
||||
this._initialize()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
_initialize () {
|
||||
this.setCodeContent(this.value)
|
||||
},
|
||||
handleChange(e){
|
||||
this.$emit('input', e.target.value)
|
||||
},
|
||||
getCodeContent(){
|
||||
return this.value
|
||||
},
|
||||
setCodeContent(val){
|
||||
setTimeout(()=>{
|
||||
if(!val){
|
||||
this.textareaValue = ''
|
||||
}else{
|
||||
this.textareaValue = val
|
||||
}
|
||||
},300)
|
||||
},
|
||||
nullTipClick(){
|
||||
this.coder.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.code-editor-cust{
|
||||
flex-grow:1;
|
||||
display:flex;
|
||||
position:relative;
|
||||
height:100%;
|
||||
.CodeMirror{
|
||||
flex-grow:1;
|
||||
z-index:1;
|
||||
.CodeMirror-code{
|
||||
line-height:19px;
|
||||
}
|
||||
|
||||
}
|
||||
.code-mode-select{
|
||||
position:absolute;
|
||||
z-index:2;
|
||||
right:10px;
|
||||
top:10px;
|
||||
max-width:130px;
|
||||
}
|
||||
.CodeMirror{
|
||||
height: auto;
|
||||
min-height:100%;
|
||||
}
|
||||
.null-tip{
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 36px;
|
||||
z-index: 10;
|
||||
color: #ffffffc9;
|
||||
line-height: initial;
|
||||
}
|
||||
.null-tip-hidden{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 全屏样式 */
|
||||
.full-screen-parent {
|
||||
position: relative;
|
||||
|
||||
.full-screen-icon {
|
||||
opacity: 0;
|
||||
color: black;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.full-screen-icon {
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.full-screen {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.full-screen-icon {
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.full-screen-child {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.auto-height {
|
||||
.full-screen-child {
|
||||
min-height: 120px;
|
||||
max-height: 320px;
|
||||
height: unset;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.full-screen .full-screen-child {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.CodeMirror-cursor{
|
||||
height:18.4px !important;
|
||||
}
|
||||
</style>
|
|
@ -39,7 +39,7 @@
|
|||
<div class="thead" ref="thead">
|
||||
<div class="tr" :style="{width: this.realTrWidth}">
|
||||
<!-- 左侧固定td -->
|
||||
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
|
||||
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft">
|
||||
<span></span>
|
||||
</div>
|
||||
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
|
||||
|
@ -104,7 +104,7 @@
|
|||
>
|
||||
<!-- 左侧固定td -->
|
||||
|
||||
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs" @dblclick="_handleRowInsertDown(rowIndex)" >
|
||||
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft" @dblclick="_handleRowInsertDown(rowIndex)" >
|
||||
<a-dropdown :trigger="['click']" :getPopupContainer="getParentContainer">
|
||||
<div class="td-ds-icons">
|
||||
<a-icon type="align-left"/>
|
||||
|
@ -584,7 +584,7 @@
|
|||
height: '32px'
|
||||
}"
|
||||
>
|
||||
<div v-if="dragSort" class="td td-ds" :style="style.tdLeftDs">
|
||||
<div v-if="dragSort" class="td td-ds" :style="style.tdLeft">
|
||||
</div>
|
||||
<div v-if="rowSelection" class="td td-cb" :style="style.tdLeft">
|
||||
统计
|
||||
|
@ -673,7 +673,7 @@
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 页面是否在加载中
|
||||
// 表格内容区域最大高度
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: 400
|
||||
|
@ -725,8 +725,7 @@
|
|||
// 'max-height': '400px'
|
||||
tbody: { left: '0px' },
|
||||
// 左侧固定td的style
|
||||
tdLeft: { 'min-width': '4%', 'max-width': '45px' },
|
||||
tdLeftDs: { 'min-width': '30px', 'max-width': '35px' },
|
||||
tdLeft: {},
|
||||
},
|
||||
// 表单的类型
|
||||
formTypes: FormTypes,
|
||||
|
@ -1398,7 +1397,6 @@
|
|||
let values = []
|
||||
// 遍历inputValues来获取每行的值
|
||||
for (let value of inputValues) {
|
||||
if (value == null) console.warn(this.caseId, '+++++++++ value.1: ', value, cloneObject(inputValues))
|
||||
let rowIdsFlag = false
|
||||
// 如果带有rowIds,那么就只存这几行的数据
|
||||
if (rowIds == null) {
|
||||
|
@ -1591,78 +1589,68 @@
|
|||
rowKey = this.getCleanId(rowKey)
|
||||
for (let newValueKey in newValues) {
|
||||
if (newValues.hasOwnProperty(newValueKey)) {
|
||||
let newValue = newValues[newValueKey]
|
||||
let edited = false // 已被修改
|
||||
this.inputValues.forEach(value => {
|
||||
// 在inputValues中找到了该字段
|
||||
if (rowKey === this.getCleanId(value.id)) {
|
||||
if (value.hasOwnProperty(newValueKey)) {
|
||||
edited = true
|
||||
value[newValueKey] = newValue
|
||||
}
|
||||
}
|
||||
})
|
||||
let modelKey = `${newValueKey}${this.caseId}${rowKey}`
|
||||
// 在 selectValues 中寻找值
|
||||
if (!edited) {
|
||||
if (newValue !== 0 && !newValue) {
|
||||
edited = this.setOneValue(this.selectValues, modelKey, undefined)
|
||||
} else {
|
||||
edited = this.setOneValue(this.selectValues, modelKey, newValue)
|
||||
}
|
||||
}
|
||||
// 在 checkboxValues 中寻找值
|
||||
if (!edited) {
|
||||
// checkbox 特殊处理 CustomValue
|
||||
let key = this.valuesHasOwnProperty(this.checkboxValues, modelKey)
|
||||
// 找到对应的column
|
||||
let sourceValue
|
||||
for (let column of this.columns) {
|
||||
if (column.key === newValueKey) {
|
||||
edited = true
|
||||
// 判断是否设定了customValue(自定义值)
|
||||
if (column.customValue instanceof Array) {
|
||||
let customValue = (column.customValue[0] || '').toString()
|
||||
sourceValue = (newValue === customValue)
|
||||
} else {
|
||||
sourceValue = !!newValue
|
||||
for (let column of this.columns) {
|
||||
if (column.key === newValueKey) {
|
||||
let newValue = newValues[newValueKey]
|
||||
this.inputValues.forEach(value => {
|
||||
// 在inputValues中找到了该字段
|
||||
if (rowKey === this.getCleanId(value.id)) {
|
||||
if (value.hasOwnProperty(newValueKey)) {
|
||||
edited = true
|
||||
value[newValueKey] = newValue
|
||||
}
|
||||
}
|
||||
this.$set(this.checkboxValues, key, sourceValue)
|
||||
break
|
||||
})
|
||||
if (!edited) {
|
||||
let modelKey = `${newValueKey}${this.caseId}${rowKey}`
|
||||
if (column.type === FormTypes.select) {
|
||||
if (newValue !== 0 && !newValue) {
|
||||
edited = this.setOneValue(this.selectValues, modelKey, undefined)
|
||||
} else {
|
||||
edited = this.setOneValue(this.selectValues, modelKey, newValue)
|
||||
}
|
||||
} else if (column.type === FormTypes.checkbox) {
|
||||
// checkbox 特殊处理 CustomValue
|
||||
let key = this.valuesHasOwnProperty(this.checkboxValues, modelKey)
|
||||
// 找到对应的column
|
||||
let sourceValue
|
||||
// 判断是否设定了customValue(自定义值)
|
||||
if (column.customValue instanceof Array) {
|
||||
let customValue = (column.customValue[0] || '').toString()
|
||||
sourceValue = (newValue === customValue)
|
||||
} else {
|
||||
sourceValue = !!newValue
|
||||
}
|
||||
this.$set(this.checkboxValues, key, sourceValue)
|
||||
edited = true
|
||||
} else if (column.type === FormTypes.date || column.type === FormTypes.datetime) {
|
||||
edited = this.setOneValue(this.jdateValues, modelKey, newValue)
|
||||
} else if (column.type === FormTypes.input_pop) {
|
||||
edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
|
||||
} else if (column.type === FormTypes.slot) {
|
||||
edited = this.setOneValue(this.slotValues, modelKey, newValue)
|
||||
} else if (column.type === FormTypes.upload || column.type === FormTypes.image || column.type === FormTypes.file) {
|
||||
edited = this.setOneValue(this.uploadValues, modelKey, newValue)
|
||||
} else if (column.type === FormTypes.popup) {
|
||||
edited = this.setOneValue(this.popupValues, modelKey, newValue)
|
||||
} else if (column.type === FormTypes.radio) {
|
||||
edited = this.setOneValue(this.radioValues, modelKey, newValue)
|
||||
} else if (column.type === FormTypes.list_multi) {
|
||||
edited = this.setOneValue(this.multiSelectValues, modelKey, newValue, true)
|
||||
} else if (column.type === FormTypes.sel_search) {
|
||||
edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
|
||||
} else {
|
||||
edited = false
|
||||
}
|
||||
}
|
||||
if (edited) {
|
||||
this.elemValueChange(column.type, {[newValueKey]: newValue}, column, newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 在 jdateValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.jdateValues, modelKey, newValue)
|
||||
}
|
||||
// 在 jInputPopValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.jInputPopValues, modelKey, newValue)
|
||||
}
|
||||
// 在 slotValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.slotValues, modelKey, newValue)
|
||||
}
|
||||
// 在 uploadValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.uploadValues, modelKey, newValue)
|
||||
}
|
||||
// 在 popupValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.popupValues, modelKey, newValue)
|
||||
}
|
||||
// 在 radioValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.radioValues, modelKey, newValue)
|
||||
}
|
||||
// 在 multiSelectValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.multiSelectValues, modelKey, newValue, true)
|
||||
}
|
||||
// 在 searchSelectValues 中寻找值
|
||||
if (!edited) {
|
||||
edited = this.setOneValue(this.searchSelectValues, modelKey, newValue)
|
||||
console.warn(`JEditableTable.setValues:没有找到"${newValueKey}"列`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2209,6 +2197,9 @@
|
|||
value['message'] = file.response.message || '未知错误'
|
||||
}
|
||||
this.uploadValues = this.bindValuesChange(value, id, 'uploadValues')
|
||||
|
||||
// 触发valueChange 事件
|
||||
this.elemValueChange(column.type, row, column, value)
|
||||
},
|
||||
handleMoreOperation(id,flag){
|
||||
//console.log("this.uploadValues[id]",this.uploadValues[id])
|
||||
|
@ -2747,8 +2738,9 @@
|
|||
flex-direction: column;
|
||||
|
||||
&.td-cb, &.td-num {
|
||||
min-width: 4%;
|
||||
max-width: 45px;
|
||||
width: 45px;
|
||||
min-width: 45px;
|
||||
max-width: 50px;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
@ -2757,6 +2749,9 @@
|
|||
}
|
||||
|
||||
&.td-ds {
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
max-width: 35px;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
@ -2942,6 +2937,10 @@
|
|||
|
||||
label {
|
||||
height: 32px;
|
||||
|
||||
&.ant-checkbox-wrapper {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.j-td-span {
|
||||
|
|
|
@ -128,6 +128,13 @@
|
|||
this.reload()
|
||||
}
|
||||
})
|
||||
}else{
|
||||
//update--begin--autor:wangshuai-----date:20200724------for:富文本编辑器切换tab无法修改------
|
||||
let tabLayout = getVmParentByName(this, 'TabLayout')
|
||||
tabLayout.excuteCallback(()=>{
|
||||
this.reload()
|
||||
})
|
||||
//update--begin--autor:wangshuai-----date:20200724------for:文本编辑器切换tab无法修改------
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -171,8 +171,14 @@
|
|||
if(!this.isMultiple){
|
||||
arr.push(uploadFiles[uploadFiles.length-1].response.message)
|
||||
}else{
|
||||
for(var a=0;a<uploadFiles.length;a++){
|
||||
arr.push(uploadFiles[a].response.message)
|
||||
for(let a=0;a<uploadFiles.length;a++){
|
||||
// update-begin-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
if(uploadFiles[a].status === 'done' ) {
|
||||
arr.push(uploadFiles[a].response.message)
|
||||
}else{
|
||||
return;
|
||||
}
|
||||
// update-end-author:taoyan date:20200819 for:【开源问题z】上传图片组件 LOWCOD-783
|
||||
}
|
||||
}
|
||||
if(arr.length>0){
|
||||
|
|
|
@ -24,6 +24,11 @@
|
|||
type:String,
|
||||
required:false,
|
||||
default:''
|
||||
},
|
||||
trim:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
|
@ -56,7 +61,10 @@
|
|||
let text = this.value
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
text = text.substring(1,text.length-1);
|
||||
//修复路由传参的值传送到jinput框被前后各截取了一位 #1336
|
||||
if(text.indexOf("*") != -1){
|
||||
text = text.substring(1,text.length-1);
|
||||
}
|
||||
break;
|
||||
case JINPUT_QUERY_NE:
|
||||
text = text.substring(1);
|
||||
|
@ -74,6 +82,9 @@
|
|||
},
|
||||
backValue(e){
|
||||
let text = e.target.value
|
||||
if(text && this.trim===true){
|
||||
text = text.trim()
|
||||
}
|
||||
switch (this.type) {
|
||||
case JINPUT_QUERY_LIKE:
|
||||
text = "*"+text+"*";
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
return Object.keys(this.$scopedSlots).filter(key => !this.usedSlots.includes(key))
|
||||
},
|
||||
allSlotsKeys() {
|
||||
return this.slotsKeys.concat(this.scopedSlotsKeys)
|
||||
return Object.keys(this.$slots).concat(Object.keys(this.$scopedSlots))
|
||||
},
|
||||
// 切换全屏的按钮图标
|
||||
fullscreenButtonIcon() {
|
||||
|
|
|
@ -498,10 +498,9 @@
|
|||
} else {
|
||||
if (Array.isArray(item.options)) {
|
||||
// 如果有字典属性,就不需要保存 options 了
|
||||
if (item.dictCode) {
|
||||
// 去掉特殊属性
|
||||
delete item.options
|
||||
}
|
||||
//update-begin-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
delete item.options
|
||||
//update-end-author:taoyan date:20200819 for:【开源问题】 高级查询 下拉框作为并且选项很多多多 LOWCOD-779
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<a-time-picker
|
||||
:disabled="disabled || readOnly"
|
||||
:placeholder="placeholder"
|
||||
:value="momVal"
|
||||
:format="dateFormat"
|
||||
:getCalendarContainer="getCalendarContainer"
|
||||
@change="handleTimeChange"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
export default {
|
||||
name: 'JTime',
|
||||
props: {
|
||||
placeholder:{
|
||||
type: String,
|
||||
default: '',
|
||||
required: false
|
||||
},
|
||||
value:{
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
dateFormat:{
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
required: false
|
||||
},
|
||||
readOnly:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disabled:{
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
getCalendarContainer: {
|
||||
type: Function,
|
||||
default: (node) => node.parentNode
|
||||
}
|
||||
},
|
||||
data () {
|
||||
let timeStr = this.value;
|
||||
return {
|
||||
decorator:"",
|
||||
momVal:!timeStr?null:moment(timeStr,this.dateFormat)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value (val) {
|
||||
if(!val){
|
||||
this.momVal = null
|
||||
}else{
|
||||
this.momVal = moment(val,this.dateFormat)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
moment,
|
||||
handleTimeChange(mom,timeStr){
|
||||
this.$emit('change', timeStr);
|
||||
}
|
||||
},
|
||||
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,75 @@
|
|||
<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) {
|
||||
let {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>
|
|
@ -0,0 +1,67 @@
|
|||
<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>
|
|
@ -0,0 +1,164 @@
|
|||
<template>
|
||||
<a-popover :visible="visible" placement="bottom" 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
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
|
||||
toggle(event) {
|
||||
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
|
||||
}
|
||||
|
||||
let {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
|
||||
}
|
||||
let table = $table.$el
|
||||
let tr = getParentNodeByTagName(target, 'tr')
|
||||
if (table && tr) {
|
||||
let clientWidth = table.clientWidth
|
||||
let 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
|
||||
domAlign(this.$refs.div, tr, {
|
||||
points: ['tl', 'tl'],
|
||||
offset: [0, 0],
|
||||
overflow: {
|
||||
alwaysByViewport: true
|
||||
},
|
||||
})
|
||||
this.$nextTick(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
triggerWindowResizeEvent()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
let 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
|
@ -0,0 +1,127 @@
|
|||
<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() {
|
||||
let arr = this.toolbarConfig.btn || ['add', 'remove', 'clearSelection']
|
||||
let exclude = [...this.excludeCode]
|
||||
// TODO 需要将remove替换batch_delete
|
||||
// 系统默认的批量删除编码配置为 batch_delete 此处需要转化一下
|
||||
if(exclude.indexOf('batch_delete')>=0){
|
||||
exclude.add('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')) {
|
||||
// 有禁用行时才显示清空选择按钮
|
||||
// 因为禁用行会阻止选择行,导致无法取消全选
|
||||
let 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>
|
|
@ -0,0 +1,103 @@
|
|||
<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/jeecg/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) {
|
||||
let {own: col} = this.column
|
||||
// 处理 customValue
|
||||
if (Array.isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col)
|
||||
if (typeof value === 'boolean') {
|
||||
return value ? customValue[0] : customValue[1]
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setValue(value) {
|
||||
let {own: col} = this.column
|
||||
// 判断是否设定了customValue(自定义值)
|
||||
if (Array.isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col)
|
||||
return neverNull(value).toString() === customValue[0].toString()
|
||||
} else {
|
||||
return !!value
|
||||
}
|
||||
},
|
||||
createValue({column}) {
|
||||
let {own: col} = column
|
||||
if (Array.isArray(col.customValue)) {
|
||||
let customValue = getCustomValue(col)
|
||||
return col.defaultChecked ? customValue[0] : customValue[1]
|
||||
} else {
|
||||
return !!col.defaultChecked
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function getCustomValue(col) {
|
||||
let customTrue = neverNull(col.customValue[0], true)
|
||||
let 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>
|
|
@ -0,0 +1,66 @@
|
|||
<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 '@/components/jeecg/JVxeTable/index'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDateCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
innerDateValue: null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDatetime() {
|
||||
return this.$type === JVXETypes.datetime
|
||||
},
|
||||
dateFormat() {
|
||||
let format = this.originColumn.format
|
||||
return format ? 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(event, 'ant-calendar-picker', el => el.children[0].dispatchEvent(event.$event)),
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,138 @@
|
|||
<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/jeecg/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.rows.length - 1)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/** 向上移 */
|
||||
handleRowMoveUp(event) {
|
||||
// event.target.blur()
|
||||
if (!this.disabledMoveUp) {
|
||||
this.trigger('rowMoveUp', this.rowIndex)
|
||||
}
|
||||
},
|
||||
/** 向下移 */
|
||||
handleRowMoveDown(event) {
|
||||
// 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>
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<a-input
|
||||
ref="input"
|
||||
:value="innerValue"
|
||||
v-bind="cellProps"
|
||||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
import JVxeCellMixins from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
const NumberRegExp = /^-?\d+\.?\d*$/
|
||||
export default {
|
||||
name: 'JVxeInputCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
methods: {
|
||||
|
||||
/** 处理change事件 */
|
||||
handleChange(event) {
|
||||
let {$type} = this
|
||||
let {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) {
|
||||
let {$type} = this
|
||||
let {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>
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<reload-effect
|
||||
:vNode="innerValue"
|
||||
:effect="reloadEffect"
|
||||
@effect-end="handleEffectEnd"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ReloadEffect from './ReloadEffect'
|
||||
import JVxeCellMixins from '@/components/jeecg/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>
|
|
@ -0,0 +1,60 @@
|
|||
<template>
|
||||
<a-progress
|
||||
:class="clazz"
|
||||
:percent="innerValue"
|
||||
size="small"
|
||||
v-bind="cellProps"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeCellMixins from '@/components/jeecg/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>
|
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<a-select
|
||||
ref="select"
|
||||
:value="innerValue"
|
||||
allowClear
|
||||
:filterOption="handleSelectFilterOption"
|
||||
v-bind="selectProps"
|
||||
style="width: 100%;"
|
||||
@blur="handleBlur"
|
||||
@change="handleChangeCommon"
|
||||
@search="handleSearchSelect"
|
||||
>
|
||||
|
||||
<template v-for="option of originColumn.options">
|
||||
<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/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
import { JVXETypes } from '@comp/jeecg/JVxeTable/index'
|
||||
|
||||
export default {
|
||||
name: 'JVxeSelectCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
computed: {
|
||||
selectProps() {
|
||||
let props = {...this.cellProps}
|
||||
// 判断select是否允许输入
|
||||
let {allowSearch, allowInput} = this.originColumn
|
||||
if (allowInput === true || allowSearch === true) {
|
||||
props['showSearch'] = true
|
||||
}
|
||||
return props
|
||||
},
|
||||
},
|
||||
created() {
|
||||
let multiple = [JVXETypes.selectMultiple, JVXETypes.list_multi]
|
||||
let search = [JVXETypes.selectSearch, JVXETypes.sel_search]
|
||||
if (multiple.includes(this.$type)) {
|
||||
// 处理多选
|
||||
let 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: {
|
||||
|
||||
/** 处理blur失去焦点事件 */
|
||||
handleBlur(value) {
|
||||
let {allowInput, options} = this.originColumn
|
||||
|
||||
if (allowInput === true) {
|
||||
// 删除无用的因搜索(用户输入)而创建的项
|
||||
if (typeof value === 'string') {
|
||||
let indexes = []
|
||||
options.forEach((option, index) => {
|
||||
if (option.value.toLocaleString() === value.toLocaleString()) {
|
||||
delete option.searchAdd
|
||||
} else if (option.searchAdd === true) {
|
||||
indexes.push(index)
|
||||
}
|
||||
})
|
||||
// 翻转删除数组中的项
|
||||
for (let index of indexes.reverse()) {
|
||||
options.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.handleBlurCommon(value)
|
||||
},
|
||||
|
||||
/** 用于搜索下拉框中的内容 */
|
||||
handleSelectFilterOption(input, option) {
|
||||
let {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) {
|
||||
let {allowSearch, allowInput, options} = this.originColumn
|
||||
|
||||
if (allowSearch !== true && allowInput === true) {
|
||||
// 是否找到了对应的项,找不到则添加这一项
|
||||
let flag = false
|
||||
for (let 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})
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
aopEvents: {
|
||||
editActived: event => dispatchEvent(event, 'ant-select'),
|
||||
},
|
||||
translate: {enabled: true},
|
||||
getValue(value) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(',')
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
},
|
||||
setValue(value) {
|
||||
let {column: {own: 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>
|
|
@ -0,0 +1,46 @@
|
|||
import JVxeCellMixins from '@/components/jeecg/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) {
|
||||
let {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)"
|
|
@ -0,0 +1,145 @@
|
|||
import JVxeCellMixins from '@/components/jeecg/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(';')) {
|
||||
let rv = replaceValue(value)
|
||||
this.innerTags = rv.split(';')
|
||||
this.handleChangeCommon(rv)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
renderTags(h) {
|
||||
let tags = []
|
||||
for (let tag of this.innerTags) {
|
||||
if (tag) {
|
||||
let tagProps = {}
|
||||
let tagStyle = {}
|
||||
let 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背景颜色
|
||||
*/
|
||||
let 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) => {
|
||||
let {target, target: {value}} = event
|
||||
let newValue = this.handleInputChange(value, event)
|
||||
if (newValue !== value) {
|
||||
target.value = newValue
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
// 将值每隔两位加上一个分号
|
||||
function replaceValue(value, event) {
|
||||
if (value) {
|
||||
// 首先去掉现有的分号
|
||||
value = value.replace(/;/g, '')
|
||||
// 然后再遍历添加分号
|
||||
let rv = ''
|
||||
let splitArr = value.split('')
|
||||
let count = 0
|
||||
splitArr.forEach((val, index) => {
|
||||
rv += val
|
||||
let position = index + 1
|
||||
if (position % 2 === 0 && position < splitArr.length) {
|
||||
count++
|
||||
rv += ';'
|
||||
}
|
||||
})
|
||||
if (event && count > 0) {
|
||||
let {target, target: {selectionStart}} = event
|
||||
target.selectionStart = selectionStart + count
|
||||
target.selectionEnd = selectionStart + count
|
||||
}
|
||||
return rv
|
||||
}
|
||||
return ''
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<j-input-pop
|
||||
:value="innerValue"
|
||||
:width="300"
|
||||
:height="210"
|
||||
v-bind="cellProps"
|
||||
style="width: 100%;"
|
||||
@change="handleChangeCommon"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JInputPop from '@/components/jeecg/minipop/JInputPop'
|
||||
import JVxeCellMixins, { dispatchEvent } from '@/components/jeecg/JVxeTable/mixins/JVxeCellMixins'
|
||||
|
||||
export default {
|
||||
name: 'JVxeTextareaCell',
|
||||
mixins: [JVxeCellMixins],
|
||||
components: {JInputPop},
|
||||
// 【组件增强】注释详见:JVxeCellMixins.js
|
||||
enhanced: {
|
||||
installOptions: {
|
||||
autofocus: '.ant-input',
|
||||
},
|
||||
aopEvents: {
|
||||
editActived: event => dispatchEvent(event, 'anticon-fullscreen'),
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,178 @@
|
|||
<template>
|
||||
<div>
|
||||
<template v-if="hasFile" 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="上传失败">
|
||||
<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"/> 预览</span>-->
|
||||
<!-- </a-menu-item>-->
|
||||
<a-menu-item v-if="originColumn.allowDownload !== false" @click="handleClickDownloadFile">
|
||||
<span><a-icon type="download"/> 下载</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="originColumn.allowRemove !== false" @click="handleClickDeleteFile">
|
||||
<span><a-icon type="delete"/> 删除</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
</a-input>
|
||||
</template>
|
||||
<a-upload
|
||||
v-show="!hasFile"
|
||||
name="file"
|
||||
:data="{'isup': 1}"
|
||||
:multiple="false"
|
||||
:action="originColumn.action"
|
||||
: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/jeecg/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() {
|
||||
let {originColumn: col} = this
|
||||
let headers = {}
|
||||
if (col.token === true) {
|
||||
headers['X-Access-Token'] = this.$ls.get(ACCESS_TOKEN)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
|
||||
hasFile() {
|
||||
return this.innerFile != null
|
||||
},
|
||||
|
||||
},
|
||||
watch: {
|
||||
innerValue: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
if (this.innerValue) {
|
||||
this.innerFile = this.innerValue
|
||||
} else {
|
||||
this.innerFile = null
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleChangeUpload(info) {
|
||||
let {row, originColumn: col} = this
|
||||
let {file} = info
|
||||
let 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') {
|
||||
value['path'] = file.response[col.responseName]
|
||||
this.handleChangeCommon(value)
|
||||
} else if (file.status === 'error') {
|
||||
value['message'] = file.response.message || '未知错误'
|
||||
}
|
||||
this.innerFile = value
|
||||
},
|
||||
|
||||
// handleClickPreviewFile(id) {
|
||||
// this.$message.info('尚未实现')
|
||||
// },
|
||||
|
||||
handleClickDownloadFile(id) {
|
||||
let {path} = this.value || {}
|
||||
if (path) {
|
||||
let 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) {
|
||||
let first = value.split(',')[0]
|
||||
let name = first.substring(first.lastIndexOf('/') + 1)
|
||||
return {
|
||||
name: name,
|
||||
path: value,
|
||||
status: 'done',
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,84 @@
|
|||
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) {
|
||||
let topLayer = this.renderSpan(old, 'top')
|
||||
this.effectList.push(topLayer)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 条件渲染内容 span
|
||||
renderVNode() {
|
||||
if (this.vNode == null) {
|
||||
return null
|
||||
}
|
||||
let bottom = this.renderSpan(this.vNode, 'bottom')
|
||||
// 启用了特效,并且有旧数据,就渲染特效顶层
|
||||
if (this.innerEffect && this.effectList.length > 0) {
|
||||
this.$emit('effect-begin')
|
||||
// 1.4s 以后关闭特效
|
||||
window.setTimeout(() => {
|
||||
let 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) {
|
||||
let 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()])
|
||||
},
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
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'
|
||||
|
||||
// 组件类型
|
||||
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',
|
||||
|
||||
// 拖轮Tags(暂无用)
|
||||
tags: 'tags',
|
||||
|
||||
slot: 'slot',
|
||||
normal: 'normal',
|
||||
hidden: 'hidden',
|
||||
}
|
||||
|
||||
// 注册自定义组件
|
||||
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),
|
||||
|
||||
/* hidden 是特殊的组件,不在这里注册 */
|
||||
}
|
||||
|
||||
export { installCell, mapCell }
|
||||
|
||||
export default JVxeTable
|
|
@ -0,0 +1,105 @@
|
|||
import Vue from 'vue'
|
||||
import { getEventPath } 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/jeecg/JVxeTable/utils/cellUtils'
|
||||
|
||||
// VxeGrid所有的方法映射
|
||||
const VxeGridMethodsMap = {}
|
||||
Object.keys(Grid.methods).forEach(key => {
|
||||
// 使用eval可以避免闭包(但是要注意不要写es6的代码)
|
||||
VxeGridMethodsMap[key] = eval(`(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, event, target) {
|
||||
// 获取组件增强
|
||||
let col = params.column.own
|
||||
const interceptor = getEnhancedMixins(col.$type, 'interceptor')
|
||||
// 执行增强
|
||||
let flag = interceptor['event.clearActived'].apply(this, arguments)
|
||||
if (flag === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
let path = getEventPath(event)
|
||||
for (let 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')) {
|
||||
return false
|
||||
}
|
||||
// 执行增强
|
||||
let 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) {
|
||||
let cells = {[type]: cell}
|
||||
if (span) {
|
||||
cells[type + ':span'] = span
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册自定义组件
|
||||
*
|
||||
* @param type 类型
|
||||
* @param cell 输入组件
|
||||
* @param span 显示组件,可空,默认为 JVxeNormalCell 组件
|
||||
*/
|
||||
export function installCell(type, cell, span) {
|
||||
let 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)
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
@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%;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
.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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
.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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
import PropTypes from 'ant-design-vue/es/_util/vue-types'
|
||||
import { filterDictText } from '@/components/dict/JDictSelectUtil'
|
||||
import { getEnhancedMixins, JVXERenderType, replaceProps } from '@/components/jeecg/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.own
|
||||
},
|
||||
$type() {
|
||||
return this.originColumn.$type
|
||||
},
|
||||
rows() {
|
||||
return this.params.data
|
||||
},
|
||||
rowIndex() {
|
||||
return this.params.rowIndex
|
||||
},
|
||||
columnIndex() {
|
||||
return this.params.columnIndex
|
||||
},
|
||||
cellProps() {
|
||||
let {originColumn: col, renderOptions} = this
|
||||
|
||||
let 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
|
||||
|
||||
// 验证值格式
|
||||
let originValue = this.row[this.column.property]
|
||||
let 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) {
|
||||
this.innerValue = this.enhanced.translate.handler.call(this, value)
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 通用处理change事件 */
|
||||
handleChangeCommon(value) {
|
||||
let 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 = []) {
|
||||
let 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
|
||||
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.own.defaultValue
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function getListeners() {
|
||||
let 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) {
|
||||
window.setTimeout(() => {
|
||||
let element = cell.getElementsByClassName(className)
|
||||
if (element && element.length > 0) {
|
||||
if (typeof handler === 'function') {
|
||||
handler(element[0])
|
||||
} else {
|
||||
// 模拟触发点击事件
|
||||
element[0].dispatchEvent($event)
|
||||
}
|
||||
}
|
||||
}, 10)
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
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['domianURL'].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 {
|
||||
let 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) {
|
||||
let binds = map.get(key)
|
||||
if (Array.isArray(binds)) {
|
||||
binds.push(value)
|
||||
} else {
|
||||
map.set(key, [value])
|
||||
}
|
||||
},
|
||||
/** 移除绑定 */
|
||||
removeBind(map, key, value) {
|
||||
let binds = map.get(key)
|
||||
if (Array.isArray(binds)) {
|
||||
for (let i = 0; i < binds.length; i++) {
|
||||
let 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) {
|
||||
let 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
|
||||
}
|
||||
let type = json[this.constants.TYPE]
|
||||
let 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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
import { AllCells, JVXETypes } from '@/components/jeecg/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]
|
||||
}
|
||||
// 添加渲染
|
||||
VXETable.renderer.add(JVXETypes._prefix + type, {
|
||||
// 可编辑模板
|
||||
renderEdit: createRender(comp, enhanced, JVXERenderType.editer),
|
||||
// 显示模板
|
||||
renderCell: createRender(spanComp, enhanced, JVXERenderType.spaner),
|
||||
// 增强注册
|
||||
...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,
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
let 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))
|
||||
}
|
||||
let defEnhanced = JVxeCellMixins.enhanced
|
||||
let enhanced = getEnhanced(type)
|
||||
if (enhanced) {
|
||||
Object.keys(defEnhanced).forEach(key => {
|
||||
let def = defEnhanced[key]
|
||||
if (enhanced.hasOwnProperty(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
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
import { getVmParentByName } from '@/utils/util'
|
||||
import { JVXETypes } from '@comp/jeecg/JVxeTable/index'
|
||||
|
||||
export const VALIDATE_FAILED = Symbol()
|
||||
|
||||
/**
|
||||
* 获取指定的 $refs 对象
|
||||
* 有时候可能会遇到组件未挂载到页面中的情况,导致无法获取 $refs 中的某个对象
|
||||
* 这个方法可以等待挂载完成之后再返回 $refs 的对象,避免报错
|
||||
* @author sunjianlei
|
||||
**/
|
||||
export function getRefPromise(vm, name) {
|
||||
return new Promise((resolve) => {
|
||||
(function next() {
|
||||
let 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) => {
|
||||
let val = rowValue[col.key], 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 | NULL}
|
||||
*/
|
||||
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)) {
|
||||
// 高级查询所需要的参数
|
||||
let fieldList = []
|
||||
// 遍历列
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let col = columns[i]
|
||||
if (col.type === JVXETypes.rowCheckbox ||
|
||||
col.type === JVXETypes.rowRadio ||
|
||||
col.type === JVXETypes.rowExpand ||
|
||||
col.type === JVXETypes.rowNumber
|
||||
) {
|
||||
continue
|
||||
}
|
||||
let 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, idx))
|
||||
}
|
||||
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 `form 参数需要的是一个form对象,而传入的却是${typeof form}`
|
||||
}
|
||||
let dataMap = {}
|
||||
let values = await new Promise((resolve, reject) => {
|
||||
// 验证主表表单
|
||||
form.validateFields((err, values) => {
|
||||
err ? reject({error: VALIDATE_FAILED, originError: err}) : resolve(values)
|
||||
})
|
||||
})
|
||||
Object.assign(dataMap, {formValue: values})
|
||||
// 验证所有子表的表单
|
||||
let 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 `'validateTables'函数的'cases'参数需要的是一个数组,而传入的却是${typeof cases}`
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
let tablesData = []
|
||||
let index = 0
|
||||
if (!cases || cases.length === 0) {
|
||||
resolve()
|
||||
}
|
||||
(function next() {
|
||||
let 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
|
||||
let tabPane = getVmParentByName(vm, 'ATabPane')
|
||||
if (tabPane) {
|
||||
paneKey = tabPane.$vnode.key
|
||||
// 自动跳转到该表格
|
||||
if (autoJumpTab) {
|
||||
let tabs = getVmParentByName(tabPane, 'Tabs')
|
||||
tabs && tabs.setActiveKey && tabs.setActiveKey(paneKey)
|
||||
}
|
||||
}
|
||||
// 出现未验证通过的表单,不再进行下一步校验,直接返回失败
|
||||
reject({error: VALIDATE_FAILED, index, paneKey, errMap})
|
||||
}
|
||||
})
|
||||
})()
|
||||
})
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
<template>
|
||||
<div class="tinymce-containerty" :style="{width:containerWidth}">
|
||||
<textarea :id="tinymceId" class="tinymce-textarea" @change="ada"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* docs:
|
||||
* https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
|
||||
*/
|
||||
import load from './load'
|
||||
//const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
|
||||
//const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']
|
||||
// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
|
||||
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'
|
||||
|
||||
export default {
|
||||
name: 'JEditorDyn',
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
required: false,
|
||||
default: 'undo redo | formatselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link unlink image media table | removeformat | fullscreen',
|
||||
},
|
||||
menubar: {
|
||||
type: String,
|
||||
default: 'file edit insert view format table'
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
default: 360
|
||||
},
|
||||
width: {
|
||||
type: [Number, String],
|
||||
required: false,
|
||||
default: 'auto'
|
||||
},
|
||||
plugins: {
|
||||
type: [String, Array],
|
||||
default: 'lists image link media table textcolor wordcount contextmenu fullscreen'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasChange: false,
|
||||
hasInit: false,
|
||||
tinymceId: this.id,
|
||||
fullscreen: false,
|
||||
languageTypeList: {
|
||||
'en': 'en',
|
||||
'zh': 'zh_CN',
|
||||
'es': 'es_MX',
|
||||
'ja': 'ja'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerWidth() {
|
||||
const width = this.width
|
||||
if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
|
||||
return `${width}px`
|
||||
}
|
||||
return width
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() =>
|
||||
window.tinymce.get(this.tinymceId).setContent(val || ''))
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
activated() {
|
||||
if (window.tinymce) {
|
||||
this.initTinymce()
|
||||
}
|
||||
},
|
||||
deactivated() {
|
||||
this.destroyTinymce()
|
||||
},
|
||||
destroyed() {
|
||||
this.destroyTinymce()
|
||||
},
|
||||
methods: {
|
||||
ada() {
|
||||
console.log('change')
|
||||
},
|
||||
init() {
|
||||
// dynamic load tinymce from cdn
|
||||
load(tinymceCDN, (err) => {
|
||||
if (err) {
|
||||
this.$message.error(err.message)
|
||||
return
|
||||
}
|
||||
this.initTinymce()
|
||||
})
|
||||
},
|
||||
initTinymce() {
|
||||
const _this = this
|
||||
window.tinymce.init({
|
||||
selector: `#${this.tinymceId}`,
|
||||
language: this.languageTypeList['zh'],
|
||||
height: this.height,
|
||||
body_class: 'panel-body ',
|
||||
object_resizing: false,
|
||||
toolbar: this.toolbar,
|
||||
menubar: false,
|
||||
plugins: this.plugins,
|
||||
end_container_on_empty_block: true,
|
||||
powerpaste_word_import: 'clean',
|
||||
code_dialog_height: 450,
|
||||
code_dialog_width: 1000,
|
||||
advlist_bullet_styles: 'square',
|
||||
advlist_number_styles: 'default',
|
||||
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
|
||||
default_link_target: '_blank',
|
||||
link_title: false,
|
||||
nonbreaking_force_tab: true, // inserting nonbreaking space need Nonbreaking Space Plugin
|
||||
init_instance_callback: editor => {
|
||||
if (_this.value) {
|
||||
editor.setContent(_this.value)
|
||||
}
|
||||
_this.hasInit = true
|
||||
editor.on('NodeChange Change KeyUp SetContent', () => {
|
||||
this.hasChange = true
|
||||
this.$emit('input', editor.getContent())
|
||||
})
|
||||
},
|
||||
setup(editor) {
|
||||
editor.on('FullscreenStateChanged', (e) => {
|
||||
_this.fullscreen = e.state
|
||||
})
|
||||
},
|
||||
// it will try to keep these URLs intact
|
||||
// https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
|
||||
// https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
|
||||
convert_urls: false
|
||||
// 整合七牛上传
|
||||
// images_dataimg_filter(img) {
|
||||
// setTimeout(() => {
|
||||
// const $image = $(img);
|
||||
// $image.removeAttr('width');
|
||||
// $image.removeAttr('height');
|
||||
// if ($image[0].height && $image[0].width) {
|
||||
// $image.attr('data-wscntype', 'image');
|
||||
// $image.attr('data-wscnh', $image[0].height);
|
||||
// $image.attr('data-wscnw', $image[0].width);
|
||||
// $image.addClass('wscnph');
|
||||
// }
|
||||
// }, 0);
|
||||
// return img
|
||||
// },
|
||||
// images_upload_handler(blobInfo, success, failure, progress) {
|
||||
// progress(0);
|
||||
// const token = _this.$store.getters.token;
|
||||
// getToken(token).then(response => {
|
||||
// const url = response.data.qiniu_url;
|
||||
// const formData = new FormData();
|
||||
// formData.append('token', response.data.qiniu_token);
|
||||
// formData.append('key', response.data.qiniu_key);
|
||||
// formData.append('file', blobInfo.blob(), url);
|
||||
// upload(formData).then(() => {
|
||||
// success(url);
|
||||
// progress(100);
|
||||
// })
|
||||
// }).catch(err => {
|
||||
// failure('err')
|
||||
// console.log(err);
|
||||
// });
|
||||
// },
|
||||
})
|
||||
},
|
||||
destroyTinymce() {
|
||||
const tinymce = window.tinymce.get(this.tinymceId)
|
||||
if (this.fullscreen) {
|
||||
tinymce.execCommand('mceFullScreen')
|
||||
}
|
||||
|
||||
if (tinymce) {
|
||||
tinymce.destroy()
|
||||
}
|
||||
},
|
||||
setContent(value) {
|
||||
window.tinymce.get(this.tinymceId).setContent(value)
|
||||
},
|
||||
getContent() {
|
||||
window.tinymce.get(this.tinymceId).getContent()
|
||||
},
|
||||
imageSuccessCBK(arr) {
|
||||
arr.forEach(v => window.tinymce.get(this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tinymce-containerty {
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.tinymce-containerty {
|
||||
::v-deep {
|
||||
.mce-fullscreen {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tinymce-textarea {
|
||||
visibility: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.editor-custom-btn-container {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
/*z-index: 2005;*/
|
||||
}
|
||||
|
||||
.fullscreen .editor-custom-btn-container {
|
||||
z-index: 10000;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.editor-upload-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div class="j-markdown-editor" :id="dynamicId"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import load from './load'
|
||||
import { md_js, md_zh_cn_js } from './Resource'
|
||||
import defaultOptions from '@/components/jeecg/JMarkdownEditor/default-options.js'
|
||||
|
||||
export default {
|
||||
name: 'JMdEditorDyn',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default() {
|
||||
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default() {
|
||||
return defaultOptions
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'markdown'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '300px'
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'zh-CN'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
dynamicId: this.id
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
editorOptions() {
|
||||
const options = Object.assign({}, defaultOptions, this.options)
|
||||
options.initialEditType = this.mode
|
||||
options.height = this.height
|
||||
options.language = this.language
|
||||
return options
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue, preValue) {
|
||||
if (newValue !== preValue && newValue !== this.editor.getMarkdown()) {
|
||||
this.editor.setMarkdown(newValue)
|
||||
}
|
||||
},
|
||||
language(val) {
|
||||
this.destroyEditor()
|
||||
this.initEditor()
|
||||
},
|
||||
height(newValue) {
|
||||
this.editor.height(newValue)
|
||||
},
|
||||
mode(newValue) {
|
||||
this.editor.changeMode(newValue)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
destroyed() {
|
||||
this.destroyEditor()
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
|
||||
this.initEditor()
|
||||
/* load(md_js,'',()=>{
|
||||
load(md_zh_cn_js,'',()=>{
|
||||
|
||||
})
|
||||
})*/
|
||||
},
|
||||
initEditor() {
|
||||
const Editor = toastui.Editor
|
||||
this.editor = new Editor({
|
||||
el: document.getElementById(this.dynamicId),
|
||||
...this.editorOptions
|
||||
})
|
||||
if (this.value) {
|
||||
this.editor.setMarkdown(this.value)
|
||||
}
|
||||
this.editor.on('change', () => {
|
||||
this.$emit('change', this.editor.getMarkdown())
|
||||
})
|
||||
},
|
||||
destroyEditor() {
|
||||
if (!this.editor) return
|
||||
this.editor.off('change')
|
||||
this.editor.remove()
|
||||
},
|
||||
setMarkdown(value) {
|
||||
this.editor.setMarkdown(value)
|
||||
},
|
||||
getMarkdown() {
|
||||
return this.editor.getMarkdown()
|
||||
},
|
||||
setHtml(value) {
|
||||
this.editor.setHtml(value)
|
||||
},
|
||||
getHtml() {
|
||||
return this.editor.getHtml()
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
|
||||
.j-markdown-editor {
|
||||
/deep/ .tui-editor-defaultUI {
|
||||
.te-mode-switch,
|
||||
.tui-scrollsync
|
||||
{
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,325 @@
|
|||
<template>
|
||||
<div class="jeecg-editor-ty" :class="fullCoder?'jeecg-editor-max':'jeecg-editor-min'">
|
||||
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
|
||||
<textarea :id="dynamicId" />
|
||||
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden': hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import load from './load'
|
||||
import '@/assets/less/codemirror_idea.css'
|
||||
import './cm_sql_hint.js'
|
||||
import { sql_keyword } from './Resource'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'JSqlCodeEditorDyn',
|
||||
props:{
|
||||
id: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return 'vue-editor-' + new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 显示行号
|
||||
lineNumbers: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 不自适应高度的情况下生效的固定高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '240px'
|
||||
},
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autoHint:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
dynamicId: this.id,
|
||||
coder: '',
|
||||
hasCode: false,
|
||||
code: '',
|
||||
// code 编辑器 是否全屏
|
||||
fullCoder: false,
|
||||
iconType: 'fullscreen',
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
placeholderShow() {
|
||||
if (this.placeholder == null) {
|
||||
return `请在此输入javascript代码`
|
||||
} else {
|
||||
return this.placeholder
|
||||
}
|
||||
},
|
||||
nullTipStyle(){
|
||||
if (this.lineNumbers) {
|
||||
return { left: '36px' }
|
||||
} else {
|
||||
return { left: '12px' }
|
||||
}
|
||||
},
|
||||
isAutoHeight() {
|
||||
let {autoHeight} = this
|
||||
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
|
||||
autoHeight = !(isIE() || isIE11())
|
||||
} else {
|
||||
autoHeight = true
|
||||
}
|
||||
return autoHeight
|
||||
},
|
||||
fullScreenParentProps() {
|
||||
let props = {
|
||||
class: {
|
||||
'full-screen-parent': true,
|
||||
'full-screen': this.fullCoder,
|
||||
'auto-height': this.isAutoHeight
|
||||
},
|
||||
style: {}
|
||||
}
|
||||
if (this.fullCoder) {
|
||||
props.style['z-index'] = this.zIndex
|
||||
}
|
||||
if (!this.isAutoHeight) {
|
||||
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fullCoder:{
|
||||
handler(value) {
|
||||
if(value){
|
||||
this.iconType="fullscreen-exit"
|
||||
}else{
|
||||
this.iconType="fullscreen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods:{
|
||||
init(){
|
||||
this.main();
|
||||
},
|
||||
main(){
|
||||
let obj = document.getElementById(this.dynamicId);
|
||||
const that = this;
|
||||
let editor = CodeMirror.fromTextArea(obj,{
|
||||
theme:'idea',
|
||||
lineNumbers: this.lineNumbers,
|
||||
lineWrapping: true,
|
||||
mode: "sql",
|
||||
indentUnit: 1,
|
||||
indentWithTabs: true,
|
||||
styleActiveLine: true,
|
||||
/* styleSelectedText: false, */
|
||||
extraKeys: {
|
||||
"F11": function(cm) {
|
||||
that.fullCoder = !that.fullCoder
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
that.fullCoder = false
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
},
|
||||
"Alt-/": function(cm) {
|
||||
cm.showHint();
|
||||
},
|
||||
"Tab": (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add');
|
||||
} else {
|
||||
//cm.indentLine(cm.getCursor().line, "add");
|
||||
//走两格 第三格输入
|
||||
cm.replaceSelection(Array(3).join(" "), "end", "+input");
|
||||
}
|
||||
},
|
||||
"Shift-Tab": (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('subtract');
|
||||
} else {
|
||||
// cm.indentLine(cm.getCursor().line, "subtract");
|
||||
const cursor = cm.getCursor();
|
||||
// 光标回退 indexUnit 字符
|
||||
cm.setCursor({line: cursor.line, ch: cursor.ch - 4});
|
||||
}
|
||||
return ;
|
||||
}
|
||||
}
|
||||
})
|
||||
this.coder = editor
|
||||
this.addEvent();
|
||||
this.setCoderValue();
|
||||
this.addSystemHint();
|
||||
},
|
||||
setCoderValue(){
|
||||
if(this.value||this.code){
|
||||
this.hasCode=true
|
||||
this.setCodeContent(this.value || this.code)
|
||||
}else{
|
||||
this.coder.setValue('')
|
||||
this.hasCode=false
|
||||
}
|
||||
},
|
||||
getCodeContent(){
|
||||
return this.code
|
||||
},
|
||||
setCodeContent(val){
|
||||
setTimeout(()=>{
|
||||
if(!val){
|
||||
this.coder.setValue('')
|
||||
}else{
|
||||
this.coder.setValue(val)
|
||||
}
|
||||
},300)
|
||||
},
|
||||
addSystemHint(){
|
||||
this.coder.setOption('hintOptions', {
|
||||
completeSingle: false,
|
||||
tables: sql_keyword
|
||||
});
|
||||
},
|
||||
addEvent(){
|
||||
if(this.autoHint){
|
||||
this.coder.on('cursorActivity', ()=>{
|
||||
this.coder.showHint();
|
||||
});
|
||||
}
|
||||
this.coder.on('change', (coder) => {
|
||||
this.code = coder.getValue()
|
||||
if(this.code){
|
||||
this.hasCode=true
|
||||
}else{
|
||||
this.hasCode=false
|
||||
}
|
||||
if (this.$emit) {
|
||||
this.$emit('input', this.code)
|
||||
}
|
||||
});
|
||||
this.coder.on('focus', () => {
|
||||
this.hasCode=true
|
||||
});
|
||||
this.coder.on('blur', () => {
|
||||
if(this.code){
|
||||
this.hasCode=true
|
||||
}else{
|
||||
this.hasCode=false
|
||||
}
|
||||
});
|
||||
},
|
||||
loadResource(src,type){
|
||||
return new Promise((resolve,reject)=>{
|
||||
load(src,type,(msg)=>{
|
||||
if(!msg){
|
||||
resolve();
|
||||
}else{
|
||||
reject(msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
nullTipClick(){
|
||||
this.coder.focus()
|
||||
},
|
||||
fullToggle(){
|
||||
this.fullCoder = !this.fullCoder
|
||||
this.coder.setOption("fullScreen", this.fullCoder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less" >
|
||||
.jeecg-editor-ty{
|
||||
position: relative;
|
||||
|
||||
.full-screen-icon {
|
||||
opacity: 0;
|
||||
color: black;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 2px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
&:hover {
|
||||
.full-screen-icon {
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.null-tip{
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 36px;
|
||||
z-index: 10;
|
||||
font-size:16px;
|
||||
color: #acaaaac9;
|
||||
line-height: initial;
|
||||
}
|
||||
.null-tip-hidden{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.jeecg-editor-max{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
|
||||
.CodeMirror{
|
||||
position: inherit !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.full-screen-icon{
|
||||
z-index:9999;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,319 @@
|
|||
<template>
|
||||
<div class="jeecg-editor-ty" :class="fullCoder?'jeecg-editor-max':'jeecg-editor-min'">
|
||||
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
|
||||
<textarea :id="dynamicId" />
|
||||
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden': hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import '@/assets/less/codemirror_idea.css'
|
||||
import './cm_hint.js'
|
||||
|
||||
export default {
|
||||
name: 'JsCodeEditorDyn',
|
||||
props:{
|
||||
id: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return 'vue-editor-' + new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 显示行号
|
||||
lineNumbers: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 999
|
||||
},
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 不自适应高度的情况下生效的固定高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: '240px'
|
||||
},
|
||||
autoHeight: {
|
||||
type: [String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
fullScreen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
dynamicId: this.id,
|
||||
coder: '',
|
||||
hasCode: false,
|
||||
code: '',
|
||||
// code 编辑器 是否全屏
|
||||
fullCoder: false,
|
||||
iconType: 'fullscreen',
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
placeholderShow() {
|
||||
if (this.placeholder == null) {
|
||||
return `请在此输入javascript代码`
|
||||
} else {
|
||||
return this.placeholder
|
||||
}
|
||||
},
|
||||
nullTipStyle(){
|
||||
if (this.lineNumbers) {
|
||||
return { left: '36px' }
|
||||
} else {
|
||||
return { left: '12px' }
|
||||
}
|
||||
},
|
||||
isAutoHeight() {
|
||||
let {autoHeight} = this
|
||||
if (typeof autoHeight === 'string' && autoHeight.toLowerCase().trim() === '!ie') {
|
||||
autoHeight = !(isIE() || isIE11())
|
||||
} else {
|
||||
autoHeight = true
|
||||
}
|
||||
return autoHeight
|
||||
},
|
||||
fullScreenParentProps() {
|
||||
let props = {
|
||||
class: {
|
||||
'full-screen-parent': true,
|
||||
'full-screen': this.fullCoder,
|
||||
'auto-height': this.isAutoHeight
|
||||
},
|
||||
style: {}
|
||||
}
|
||||
if (this.fullCoder) {
|
||||
props.style['z-index'] = this.zIndex
|
||||
}
|
||||
if (!this.isAutoHeight) {
|
||||
props.style['height'] = (typeof this.height === 'number' ? this.height + 'px' : this.height)
|
||||
}
|
||||
return props
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
fullCoder:{
|
||||
handler(value) {
|
||||
if(value){
|
||||
this.iconType="fullscreen-exit"
|
||||
}else{
|
||||
this.iconType="fullscreen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods:{
|
||||
init(){
|
||||
this.main();
|
||||
},
|
||||
main(){
|
||||
let obj = document.getElementById(this.dynamicId);
|
||||
const that = this;
|
||||
let editor = CodeMirror.fromTextArea(obj,{
|
||||
theme:'idea',
|
||||
lineNumbers: this.lineNumbers,
|
||||
lineWrapping: true,
|
||||
mode: "javascript",
|
||||
indentUnit: 1,
|
||||
indentWithTabs: true,
|
||||
styleActiveLine: true,
|
||||
/* styleSelectedText: false, */
|
||||
extraKeys: {
|
||||
"F11": function(cm) {
|
||||
that.fullCoder = !that.fullCoder
|
||||
cm.setOption("fullScreen", !cm.getOption("fullScreen"));
|
||||
},
|
||||
"Esc": function(cm) {
|
||||
that.fullCoder = false
|
||||
if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
|
||||
},
|
||||
"Alt-/": function(cm) {
|
||||
let a = cm.getValue()+""
|
||||
console.log('a',a)
|
||||
cm.showHint();
|
||||
},
|
||||
"Tab": (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('add');
|
||||
} else {
|
||||
//cm.indentLine(cm.getCursor().line, "add");
|
||||
//走两格 第三格输入
|
||||
cm.replaceSelection(Array(3).join(" "), "end", "+input");
|
||||
}
|
||||
},
|
||||
"Shift-Tab": (cm) => {
|
||||
if (cm.somethingSelected()) {
|
||||
cm.indentSelection('subtract');
|
||||
} else {
|
||||
// cm.indentLine(cm.getCursor().line, "subtract");
|
||||
const cursor = cm.getCursor();
|
||||
// 光标回退 indexUnit 字符
|
||||
cm.setCursor({line: cursor.line, ch: cursor.ch - 4});
|
||||
}
|
||||
return ;
|
||||
}
|
||||
}
|
||||
})
|
||||
this.coder = editor
|
||||
this.addEvent();
|
||||
this.setCoderValue();
|
||||
},
|
||||
setCoderValue(){
|
||||
if(this.value||this.code){
|
||||
this.hasCode=true
|
||||
this.setCodeContent(this.value || this.code)
|
||||
}else{
|
||||
this.coder.setValue('')
|
||||
this.hasCode=false
|
||||
}
|
||||
},
|
||||
getCodeContent(){
|
||||
return this.code
|
||||
},
|
||||
setCodeContent(val){
|
||||
setTimeout(()=>{
|
||||
if(!val){
|
||||
this.coder.setValue('')
|
||||
}else{
|
||||
this.coder.setValue(val)
|
||||
}
|
||||
},300)
|
||||
},
|
||||
addEvent(){
|
||||
const that = this;
|
||||
this.coder.on('cursorActivity',function(wl) {
|
||||
let arr = wl.state.activeLines
|
||||
if(arr && arr.length>0){
|
||||
let text = arr[0].text
|
||||
if(text.lastIndexOf('that.')>=0){
|
||||
that.coder.showHint();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.coder.on('change', (coder) => {
|
||||
this.code = coder.getValue()
|
||||
if(this.code){
|
||||
this.hasCode=true
|
||||
}else{
|
||||
this.hasCode=false
|
||||
}
|
||||
if (this.$emit) {
|
||||
this.$emit('input', this.code)
|
||||
}
|
||||
});
|
||||
this.coder.on('focus', () => {
|
||||
this.hasCode=true
|
||||
});
|
||||
this.coder.on('blur', () => {
|
||||
if(this.code){
|
||||
this.hasCode=true
|
||||
}else{
|
||||
this.hasCode=false
|
||||
}
|
||||
});
|
||||
},
|
||||
loadResource(src,type){
|
||||
return new Promise((resolve,reject)=>{
|
||||
load(src,type,(msg)=>{
|
||||
if(!msg){
|
||||
resolve();
|
||||
}else{
|
||||
reject(msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
nullTipClick(){
|
||||
this.coder.focus()
|
||||
},
|
||||
fullToggle(){
|
||||
this.fullCoder = !this.fullCoder
|
||||
this.coder.setOption("fullScreen", this.fullCoder);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="less" >
|
||||
.jeecg-editor-ty{
|
||||
position: relative;
|
||||
|
||||
.full-screen-icon {
|
||||
opacity: 0;
|
||||
color: black;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 24px;
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 2px;
|
||||
z-index: 9;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
&:hover {
|
||||
.full-screen-icon {
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.null-tip{
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 36px;
|
||||
z-index: 10;
|
||||
font-size:16px;
|
||||
color: #acaaaac9;
|
||||
line-height: initial;
|
||||
}
|
||||
.null-tip-hidden{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.jeecg-editor-max{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
|
||||
.CodeMirror{
|
||||
position: inherit !important;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.full-screen-icon{
|
||||
z-index:9999;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
/**js编辑器关键词用于提示*/
|
||||
const js_keyword = [
|
||||
'that',
|
||||
'getAction','postAction','deleteAction',
|
||||
'beforeAdd','beforeEdit','beforeDelete','mounted','created','show'
|
||||
]
|
||||
|
||||
/**js编辑器 方法名用于提示*/
|
||||
const js_method = [
|
||||
'.getSelectOptions','.changeOptions','.triggleChangeValues','.immediateEnhance ','.simpleDateFormat','.lodash'
|
||||
]
|
||||
|
||||
/**sql编辑器 表名字段名用于提示*/
|
||||
const sql_keyword = {
|
||||
sys_user: ['USERNAME', 'REALNAME', 'ID','BIRTHDAY','AGE'],
|
||||
demo: ['name', 'age', 'id', 'sex']
|
||||
}
|
||||
|
||||
export {
|
||||
js_keyword,
|
||||
js_method,
|
||||
sql_keyword
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
import { js_keyword, js_method } from './Resource'
|
||||
(function(mod) {
|
||||
mod(CodeMirror);
|
||||
/*if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror"], mod);
|
||||
else // Plain browser env*/
|
||||
|
||||
})(function(CodeMirror) {
|
||||
var Pos = CodeMirror.Pos;
|
||||
|
||||
function forEach(arr, f) {
|
||||
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
|
||||
}
|
||||
|
||||
function arrayContains(arr, item) {
|
||||
if (!Array.prototype.indexOf) {
|
||||
var i = arr.length;
|
||||
while (i--) {
|
||||
if (arr[i] === item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return arr.indexOf(item) != -1;
|
||||
}
|
||||
|
||||
function scriptHint(editor, keywords, getToken, options) {
|
||||
// Find the token at the cursor
|
||||
var cur = editor.getCursor(), token = getToken(editor, cur);
|
||||
if (/\b(?:string|comment)\b/.test(token.type)) return;
|
||||
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
|
||||
if (innerMode.mode.helperType === "json") return;
|
||||
token.state = innerMode.state;
|
||||
if('.' === token.string){
|
||||
let arr = []
|
||||
for(let k of js_method){
|
||||
arr.push(k)
|
||||
}
|
||||
return {
|
||||
list: arr,
|
||||
from: Pos(cur.line, token.start),
|
||||
to: Pos(cur.line, token.end)
|
||||
};
|
||||
}
|
||||
// If it's not a 'word-style' token, ignore the token.
|
||||
if (!/^[\w$_]*$/.test(token.string)) {
|
||||
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
|
||||
type: token.string == "." ? "property" : null};
|
||||
} else if (token.end > cur.ch) {
|
||||
token.end = cur.ch;
|
||||
token.string = token.string.slice(0, cur.ch - token.start);
|
||||
}
|
||||
|
||||
var tprop = token;
|
||||
// If it is a property, find out what it is a property of.
|
||||
while (tprop.type == "property") {
|
||||
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||
if (tprop.string != ".") return;
|
||||
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
||||
if (!context) var context = [];
|
||||
context.push(tprop);
|
||||
}
|
||||
return {list: getCompletions(token, context, keywords, options),
|
||||
from: Pos(cur.line, token.start),
|
||||
to: Pos(cur.line, token.end)};
|
||||
}
|
||||
|
||||
function javascriptHint(editor, options) {
|
||||
return scriptHint(editor, javascriptKeywords,
|
||||
function (e, cur) {return e.getTokenAt(cur);},
|
||||
options);
|
||||
};
|
||||
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
||||
|
||||
function getCoffeeScriptToken(editor, cur) {
|
||||
// This getToken, it is for coffeescript, imitates the behavior of
|
||||
// getTokenAt method in javascript.js, that is, returning "property"
|
||||
// type and treat "." as indepenent token.
|
||||
var token = editor.getTokenAt(cur);
|
||||
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
|
||||
token.end = token.start;
|
||||
token.string = '.';
|
||||
token.type = "property";
|
||||
}
|
||||
else if (/^\.[\w$_]*$/.test(token.string)) {
|
||||
token.type = "property";
|
||||
token.start++;
|
||||
token.string = token.string.replace(/\./, '');
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
function coffeescriptHint(editor, options) {
|
||||
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
|
||||
}
|
||||
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
|
||||
|
||||
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
|
||||
"toUpperCase toLowerCase split concat match replace search").split(" ");
|
||||
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
|
||||
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
|
||||
var funcProps = "prototype apply call bind".split(" ");
|
||||
|
||||
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
|
||||
"if in import instanceof new null return super switch this throw true try typeof var void while with yield that").split(" ");
|
||||
for(let jk of js_keyword){
|
||||
javascriptKeywords.push(jk)
|
||||
}
|
||||
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
|
||||
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
|
||||
|
||||
function forAllProps(obj, callback) {
|
||||
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
||||
for (var name in obj) callback(name)
|
||||
} else {
|
||||
for (var o = obj; o; o = Object.getPrototypeOf(o))
|
||||
Object.getOwnPropertyNames(o).forEach(callback)
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletions(token, context, keywords, options) {
|
||||
var found = [], start = token.string, global = options && options.globalScope || window;
|
||||
function maybeAdd(str) {
|
||||
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
|
||||
}
|
||||
function gatherCompletions(obj) {
|
||||
if (typeof obj == "string") forEach(stringProps, maybeAdd);
|
||||
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
|
||||
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
|
||||
forAllProps(obj, maybeAdd)
|
||||
}
|
||||
|
||||
if (context && context.length) {
|
||||
// If this is a property, see if it belongs to some object we can
|
||||
// find in the current environment.
|
||||
var obj = context.pop(), base;
|
||||
if (obj.type && obj.type.indexOf("variable") === 0) {
|
||||
if (options && options.additionalContext)
|
||||
base = options.additionalContext[obj.string];
|
||||
if (!options || options.useGlobalScope !== false)
|
||||
base = base || global[obj.string];
|
||||
} else if (obj.type == "string") {
|
||||
base = "";
|
||||
} else if (obj.type == "atom") {
|
||||
base = 1;
|
||||
} else if (obj.type == "function") {
|
||||
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
|
||||
(typeof global.jQuery == 'function'))
|
||||
base = global.jQuery();
|
||||
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
|
||||
base = global._();
|
||||
}
|
||||
while (base != null && context.length)
|
||||
base = base[context.pop().string];
|
||||
if (base != null) gatherCompletions(base);
|
||||
} else {
|
||||
// If not, just look in the global object, any local scope, and optional additional-context
|
||||
// (reading into JS mode internals to get at the local and global variables)
|
||||
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
|
||||
for (var c = token.state.context; c; c = c.prev)
|
||||
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
|
||||
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
|
||||
if (options && options.additionalContext != null)
|
||||
for (var key in options.additionalContext)
|
||||
maybeAdd(key);
|
||||
if (!options || options.useGlobalScope !== false)
|
||||
gatherCompletions(global);
|
||||
forEach(keywords, maybeAdd);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,305 @@
|
|||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||
|
||||
(function(mod) {
|
||||
/*if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||
mod(require("../../lib/codemirror"), require("../../mode/sql/sql"));
|
||||
else if (typeof define == "function" && define.amd) // AMD
|
||||
define(["../../lib/codemirror", "../../mode/sql/sql"], mod);
|
||||
else */
|
||||
// Plain browser env
|
||||
mod(CodeMirror);
|
||||
})(function(CodeMirror) {
|
||||
"use strict";
|
||||
|
||||
var tables;
|
||||
var defaultTable;
|
||||
var keywords;
|
||||
var identifierQuote;
|
||||
var CONS = {
|
||||
QUERY_DIV: ";",
|
||||
ALIAS_KEYWORD: "AS"
|
||||
};
|
||||
var Pos = CodeMirror.Pos, cmpPos = CodeMirror.cmpPos;
|
||||
|
||||
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
|
||||
|
||||
function getKeywords(editor) {
|
||||
var mode = editor.doc.modeOption;
|
||||
if (mode === "sql") mode = "text/x-sql";
|
||||
return CodeMirror.resolveMode(mode).keywords;
|
||||
}
|
||||
|
||||
function getIdentifierQuote(editor) {
|
||||
var mode = editor.doc.modeOption;
|
||||
if (mode === "sql") mode = "text/x-sql";
|
||||
return CodeMirror.resolveMode(mode).identifierQuote || "`";
|
||||
}
|
||||
|
||||
function getText(item) {
|
||||
return typeof item == "string" ? item : item.text;
|
||||
}
|
||||
|
||||
function wrapTable(name, value) {
|
||||
if (isArray(value)) value = {columns: value}
|
||||
if (!value.text) value.text = name
|
||||
return value
|
||||
}
|
||||
|
||||
function parseTables(input) {
|
||||
var result = {}
|
||||
if (isArray(input)) {
|
||||
for (var i = input.length - 1; i >= 0; i--) {
|
||||
var item = input[i]
|
||||
result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
|
||||
}
|
||||
} else if (input) {
|
||||
for (var name in input)
|
||||
result[name.toUpperCase()] = wrapTable(name, input[name])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function getTable(name) {
|
||||
return tables[name.toUpperCase()]
|
||||
}
|
||||
|
||||
function shallowClone(object) {
|
||||
var result = {};
|
||||
for (var key in object) if (object.hasOwnProperty(key))
|
||||
result[key] = object[key];
|
||||
return result;
|
||||
}
|
||||
|
||||
function match(string, word) {
|
||||
var len = string.length;
|
||||
var sub = getText(word).substr(0, len);
|
||||
return string.toUpperCase() === sub.toUpperCase();
|
||||
}
|
||||
|
||||
function addMatches(result, search, wordlist, formatter) {
|
||||
if (isArray(wordlist)) {
|
||||
for (var i = 0; i < wordlist.length; i++)
|
||||
if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
|
||||
} else {
|
||||
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
|
||||
var val = wordlist[word]
|
||||
if (!val || val === true)
|
||||
val = word
|
||||
else
|
||||
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
|
||||
if (match(search, val)) result.push(formatter(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cleanName(name) {
|
||||
// Get rid name from identifierQuote and preceding dot(.)
|
||||
if (name.charAt(0) == ".") {
|
||||
name = name.substr(1);
|
||||
}
|
||||
// replace doublicated identifierQuotes with single identifierQuotes
|
||||
// and remove single identifierQuotes
|
||||
var nameParts = name.split(identifierQuote+identifierQuote);
|
||||
for (var i = 0; i < nameParts.length; i++)
|
||||
nameParts[i] = nameParts[i].replace(new RegExp(identifierQuote,"g"), "");
|
||||
return nameParts.join(identifierQuote);
|
||||
}
|
||||
|
||||
function insertIdentifierQuotes(name) {
|
||||
var nameParts = getText(name).split(".");
|
||||
for (var i = 0; i < nameParts.length; i++)
|
||||
nameParts[i] = identifierQuote +
|
||||
// doublicate identifierQuotes
|
||||
nameParts[i].replace(new RegExp(identifierQuote,"g"), identifierQuote+identifierQuote) +
|
||||
identifierQuote;
|
||||
var escaped = nameParts.join(".");
|
||||
if (typeof name == "string") return escaped;
|
||||
name = shallowClone(name);
|
||||
name.text = escaped;
|
||||
return name;
|
||||
}
|
||||
|
||||
function nameCompletion(cur, token, result, editor) {
|
||||
// Try to complete table, column names and return start position of completion
|
||||
var useIdentifierQuotes = false;
|
||||
var nameParts = [];
|
||||
var start = token.start;
|
||||
var cont = true;
|
||||
while (cont) {
|
||||
cont = (token.string.charAt(0) == ".");
|
||||
useIdentifierQuotes = useIdentifierQuotes || (token.string.charAt(0) == identifierQuote);
|
||||
|
||||
start = token.start;
|
||||
nameParts.unshift(cleanName(token.string));
|
||||
|
||||
token = editor.getTokenAt(Pos(cur.line, token.start));
|
||||
if (token.string == ".") {
|
||||
cont = true;
|
||||
token = editor.getTokenAt(Pos(cur.line, token.start));
|
||||
}
|
||||
}
|
||||
|
||||
// Try to complete table names
|
||||
var string = nameParts.join(".");
|
||||
addMatches(result, string, tables, function(w) {
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
|
||||
// Try to complete columns from defaultTable
|
||||
addMatches(result, string, defaultTable, function(w) {
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
|
||||
// Try to complete columns
|
||||
string = nameParts.pop();
|
||||
var table = nameParts.join(".");
|
||||
|
||||
var alias = false;
|
||||
var aliasTable = table;
|
||||
// Check if table is available. If not, find table by Alias
|
||||
if (!getTable(table)) {
|
||||
var oldTable = table;
|
||||
table = findTableByAlias(table, editor);
|
||||
if (table !== oldTable) alias = true;
|
||||
}
|
||||
|
||||
var columns = getTable(table);
|
||||
if (columns && columns.columns)
|
||||
columns = columns.columns;
|
||||
|
||||
if (columns) {
|
||||
addMatches(result, string, columns, function(w) {
|
||||
var tableInsert = table;
|
||||
if (alias == true) tableInsert = aliasTable;
|
||||
if (typeof w == "string") {
|
||||
w = tableInsert + "." + w;
|
||||
} else {
|
||||
w = shallowClone(w);
|
||||
w.text = tableInsert + "." + w.text;
|
||||
}
|
||||
return useIdentifierQuotes ? insertIdentifierQuotes(w) : w;
|
||||
});
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
function eachWord(lineText, f) {
|
||||
var words = lineText.split(/\s+/)
|
||||
for (var i = 0; i < words.length; i++)
|
||||
if (words[i]) f(words[i].replace(/[,;]/g, ''))
|
||||
}
|
||||
|
||||
function findTableByAlias(alias, editor) {
|
||||
var doc = editor.doc;
|
||||
var fullQuery = doc.getValue();
|
||||
var aliasUpperCase = alias.toUpperCase();
|
||||
var previousWord = "";
|
||||
var table = "";
|
||||
var separator = [];
|
||||
var validRange = {
|
||||
start: Pos(0, 0),
|
||||
end: Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).length)
|
||||
};
|
||||
|
||||
//add separator
|
||||
var indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV);
|
||||
while(indexOfSeparator != -1) {
|
||||
separator.push(doc.posFromIndex(indexOfSeparator));
|
||||
indexOfSeparator = fullQuery.indexOf(CONS.QUERY_DIV, indexOfSeparator+1);
|
||||
}
|
||||
separator.unshift(Pos(0, 0));
|
||||
separator.push(Pos(editor.lastLine(), editor.getLineHandle(editor.lastLine()).text.length));
|
||||
|
||||
//find valid range
|
||||
var prevItem = null;
|
||||
var current = editor.getCursor()
|
||||
for (var i = 0; i < separator.length; i++) {
|
||||
if ((prevItem == null || cmpPos(current, prevItem) > 0) && cmpPos(current, separator[i]) <= 0) {
|
||||
validRange = {start: prevItem, end: separator[i]};
|
||||
break;
|
||||
}
|
||||
prevItem = separator[i];
|
||||
}
|
||||
|
||||
if (validRange.start) {
|
||||
var query = doc.getRange(validRange.start, validRange.end, false);
|
||||
|
||||
for (var i = 0; i < query.length; i++) {
|
||||
var lineText = query[i];
|
||||
eachWord(lineText, function(word) {
|
||||
var wordUpperCase = word.toUpperCase();
|
||||
if (wordUpperCase === aliasUpperCase && getTable(previousWord))
|
||||
table = previousWord;
|
||||
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
|
||||
previousWord = word;
|
||||
});
|
||||
if (table) break;
|
||||
}
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
|
||||
tables = parseTables(options && options.tables)
|
||||
var defaultTableName = options && options.defaultTable;
|
||||
var disableKeywords = options && options.disableKeywords;
|
||||
defaultTable = defaultTableName && getTable(defaultTableName);
|
||||
keywords = getKeywords(editor);
|
||||
identifierQuote = getIdentifierQuote(editor);
|
||||
|
||||
if (defaultTableName && !defaultTable)
|
||||
defaultTable = findTableByAlias(defaultTableName, editor);
|
||||
|
||||
defaultTable = defaultTable || [];
|
||||
|
||||
if (defaultTable.columns)
|
||||
defaultTable = defaultTable.columns;
|
||||
|
||||
var cur = editor.getCursor();
|
||||
var result = [];
|
||||
var token = editor.getTokenAt(cur), start, end, search;
|
||||
if (token.end > cur.ch) {
|
||||
token.end = cur.ch;
|
||||
token.string = token.string.slice(0, cur.ch - token.start);
|
||||
}
|
||||
|
||||
if (token.string.match(/^[.`"'\w@][\w$#]*$/g)) {
|
||||
search = token.string;
|
||||
start = token.start;
|
||||
end = token.end;
|
||||
} else {
|
||||
start = end = cur.ch;
|
||||
search = "";
|
||||
}
|
||||
if (search.charAt(0) == "." || search.charAt(0) == identifierQuote) {
|
||||
start = nameCompletion(cur, token, result, editor);
|
||||
} else {
|
||||
var objectOrClass = function(w, className) {
|
||||
if (typeof w === "object") {
|
||||
w.className = className;
|
||||
} else {
|
||||
w = { text: w, className: className };
|
||||
}
|
||||
return w;
|
||||
};
|
||||
addMatches(result, search, defaultTable, function(w) {
|
||||
return objectOrClass(w, "CodeMirror-hint-table CodeMirror-hint-default-table");
|
||||
});
|
||||
addMatches(
|
||||
result,
|
||||
search,
|
||||
tables, function(w) {
|
||||
return objectOrClass(w, "CodeMirror-hint-table");
|
||||
}
|
||||
);
|
||||
if (!disableKeywords)
|
||||
addMatches(result, search, keywords, function(w) {
|
||||
return objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
|
||||
});
|
||||
}
|
||||
|
||||
return {list: result, from: Pos(cur.line, start), to: Pos(cur.line, end)};
|
||||
});
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
let callbacks = []
|
||||
|
||||
function loadSuccess(key) {
|
||||
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
|
||||
// check is successfully downloaded script
|
||||
return window[key]
|
||||
}
|
||||
|
||||
const load = (src, type, callback) => {
|
||||
if(type=='link'){
|
||||
|
||||
loadStyle(src, callback)
|
||||
}else{
|
||||
let loadKey = ''
|
||||
if(src.indexOf('tinymce')>=0){
|
||||
loadKey = 'tinymce'
|
||||
}else if(src.indexOf('codemirror')>=0){
|
||||
loadKey = 'CodeMirror'
|
||||
}
|
||||
const scriptTag = document.getElementById(src)
|
||||
//const cb = callback || function() {}
|
||||
if (!scriptTag) {
|
||||
const script = document.createElement('script')
|
||||
script.src = src // src url for the third-party library being loaded.
|
||||
script.id = src
|
||||
script.onload=()=>callback()
|
||||
script.onerror=()=>callback('加载失败:'+src)
|
||||
document.body.appendChild(script)
|
||||
//callbacks.push(cb)
|
||||
// const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
|
||||
// onEnd(script)
|
||||
}else{
|
||||
if (loadSuccess(loadKey)) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
if (scriptTag) {
|
||||
/* else {
|
||||
callbacks.push(cb)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
function stdOnEnd(script) {
|
||||
script['onload'] = function() {
|
||||
// this.onload = null here is necessary
|
||||
// because even IE9 works not like others
|
||||
this.onerror = this.onload = null
|
||||
for (const cb of callbacks) {
|
||||
cb(null, script)
|
||||
}
|
||||
callbacks = null
|
||||
}
|
||||
script['onerror'] = function() {
|
||||
this.onerror = this.onload = null
|
||||
cb(new Error('Failed to load ' + src), script)
|
||||
}
|
||||
}
|
||||
|
||||
function ieOnEnd(script) {
|
||||
script.onreadystatechange = function() {
|
||||
if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
|
||||
this.onreadystatechange = null
|
||||
for (const cb of callbacks) {
|
||||
cb(null, script) // there is no way to catch loading errors in IE8
|
||||
}
|
||||
callbacks = null
|
||||
}
|
||||
}
|
||||
|
||||
function loadStyle(src, callback) {
|
||||
const link = document.getElementById(src)
|
||||
if (!link) {
|
||||
const link = document.createElement('link')
|
||||
link.setAttribute("rel", "stylesheet");
|
||||
link.setAttribute("type", "text/css");
|
||||
link.setAttribute("href", src);
|
||||
link.id = src
|
||||
let heads = document.getElementsByTagName("head")
|
||||
if(heads.length){
|
||||
heads[0].appendChild(link)
|
||||
}else{
|
||||
document.documentElement.appendChild(link)
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default load
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-modal
|
||||
title="文件上传"
|
||||
:title="fileType === 'image' ? '图片上传' : '文件上传'"
|
||||
:width="width"
|
||||
:visible="visible"
|
||||
@ok="ok"
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<a-icon slot="suffix" type="fullscreen" @click.stop="pop" />
|
||||
</a-input>
|
||||
<div slot="content">
|
||||
<textarea :value="inputContent" :disabled="disabled" @input="handleInputChange" :style="{ height: height + 'px', width: width + 'px' }"></textarea>
|
||||
<a-textarea ref="textarea" :value="inputContent" :disabled="disabled" @input="handleInputChange" :style="{ height: height + 'px', width: width + 'px' }"/>
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
@ -84,6 +84,9 @@
|
|||
},
|
||||
pop(){
|
||||
this.visible=true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.textarea.focus()
|
||||
})
|
||||
},
|
||||
getPopupContainer(node){
|
||||
if(!this.popContainer){
|
||||
|
|
|
@ -214,8 +214,31 @@
|
|||
return filterObj(param);
|
||||
},
|
||||
handleChangeInTableSelect(selectedRowKeys, selectionRows) {
|
||||
//update-begin-author:taoyan date:2020902 for:【issue】开源online的几个问题 LOWCOD-844
|
||||
if(!selectedRowKeys || selectedRowKeys.length==0){
|
||||
this.table.selectionRows = []
|
||||
}else if(selectedRowKeys.length == selectionRows.length){
|
||||
this.table.selectionRows = selectionRows
|
||||
}else{
|
||||
//当两者长度不一的时候 需要判断
|
||||
let keys = this.table.selectedRowKeys
|
||||
let rows = this.table.selectionRows;
|
||||
//这个循环 添加新的记录
|
||||
for(let i=0;i<selectionRows.length;i++){
|
||||
let combineKey = this.combineRowKey(selectionRows[i])
|
||||
if(keys.indexOf(combineKey)<0){
|
||||
//如果 原来的key 不包含当前记录 push
|
||||
rows.push(selectionRows[i])
|
||||
}
|
||||
}
|
||||
//这个循环 移除取消选中的数据
|
||||
this.table.selectionRows = rows.filter(item=>{
|
||||
let combineKey = this.combineRowKey(item)
|
||||
return selectedRowKeys.indexOf(combineKey)>=0
|
||||
})
|
||||
}
|
||||
//update-end-author:taoyan date:2020902 for:【issue】开源online的几个问题 LOWCOD-844
|
||||
this.table.selectedRowKeys = selectedRowKeys
|
||||
this.table.selectionRows = selectionRows
|
||||
},
|
||||
handleChangeInTable(pagination, filters, sorter) {
|
||||
//分页、排序、筛选变化时触发
|
||||
|
|
|
@ -12,25 +12,21 @@
|
|||
<a-row :gutter="18">
|
||||
<a-col :span="16">
|
||||
<!-- 查询区域 -->
|
||||
<div class="table-page-search-wrapper">
|
||||
<a-form layout="inline">
|
||||
<a-row :gutter="24">
|
||||
|
||||
<a-col :span="14">
|
||||
<a-form-item :label="(queryParamText||name)">
|
||||
<a-input v-model="queryParam[queryParamCode||valueKey]" :placeholder="'请输入' + (queryParamText||name)" @pressEnter="searchQuery"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<span style="float: left;overflow: hidden;" class="table-page-search-submitButtons">
|
||||
<a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
|
||||
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
|
||||
</span>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
<a-form layout="inline" class="j-inline-form">
|
||||
<!-- 固定条件 -->
|
||||
<a-form-item :label="(queryParamText||name)">
|
||||
<a-input v-model="queryParam[queryParamCode||valueKey]" :placeholder="'请输入' + (queryParamText||name)" @pressEnter="searchQuery"/>
|
||||
</a-form-item>
|
||||
<!-- 动态生成的查询条件 -->
|
||||
<j-select-biz-query-item v-if="queryConfig.length>0" v-show="showMoreQueryItems" :queryParam="queryParam" :queryConfig="queryConfig" @pressEnter="searchQuery"/>
|
||||
<!-- 按钮 -->
|
||||
<a-button type="primary" @click="searchQuery" icon="search">查询</a-button>
|
||||
<a-button type="primary" @click="searchReset" icon="reload" style="margin-left: 8px">重置</a-button>
|
||||
<a v-if="queryConfig.length>0" @click="showMoreQueryItems=!showMoreQueryItems" style="margin-left: 8px">
|
||||
{{ showMoreQueryItems ? '收起' : '展开' }}
|
||||
<a-icon :type="showMoreQueryItems ? 'up' : 'down'"/>
|
||||
</a>
|
||||
</a-form>
|
||||
|
||||
<a-table
|
||||
size="middle"
|
||||
|
@ -67,11 +63,12 @@
|
|||
import Ellipsis from '@/components/Ellipsis'
|
||||
import { JeecgListMixin } from '@/mixins/JeecgListMixin'
|
||||
import { cloneObject, pushIfNotExist } from '@/utils/util'
|
||||
import JSelectBizQueryItem from './JSelectBizQueryItem'
|
||||
|
||||
export default {
|
||||
name: 'JSelectBizComponentModal',
|
||||
mixins: [JeecgListMixin],
|
||||
components: { Ellipsis },
|
||||
components: {Ellipsis, JSelectBizQueryItem},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
|
@ -127,6 +124,11 @@
|
|||
type: String,
|
||||
default: null
|
||||
},
|
||||
// 查询配置
|
||||
queryConfig: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
|
@ -169,6 +171,7 @@
|
|||
},
|
||||
options: [],
|
||||
dataSourceMap: {},
|
||||
showMoreQueryItems: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -299,11 +302,13 @@
|
|||
this.$emit('input', value)
|
||||
this.close()
|
||||
},
|
||||
|
||||
/** 删除已选择的 */
|
||||
handleDeleteSelected(record, index) {
|
||||
this.selectedRowKeys.splice(this.selectedRowKeys.indexOf(record[this.rowKey]), 1)
|
||||
this.selectedTable.dataSource.splice(index, 1)
|
||||
//update--begin--autor:wangshuai-----date:20200722------for:JSelectBizComponent组件切换页数值问题------
|
||||
this.selectedTable.dataSource.splice(this.selectedTable.dataSource.indexOf(record), 1)
|
||||
this.innerValue.splice(this.innerValue.indexOf(record[this.valueKey]), 1)
|
||||
//update--begin--autor:wangshuai-----date:20200722------for:JSelectBizComponent组件切换页数值问题------
|
||||
},
|
||||
|
||||
customRowFn(record) {
|
||||
|
@ -332,4 +337,29 @@
|
|||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.full-form-item {
|
||||
display: flex;
|
||||
margin-right: 0;
|
||||
|
||||
/deep/ .ant-form-item-control-wrapper {
|
||||
flex: 1 1;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.j-inline-form {
|
||||
/deep/ .ant-form-item {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
/deep/ .ant-form-item-label {
|
||||
line-height: 32px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/deep/ .ant-form-item-control {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,48 @@
|
|||
export default {
|
||||
name: 'JSelectBizQueryItem',
|
||||
props: {
|
||||
queryParam: Object,
|
||||
queryConfig: Array,
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
renderQueryItem() {
|
||||
return this.queryConfig.map(queryItem => {
|
||||
const {key, label, placeholder, dictCode, props, customRender} = queryItem
|
||||
const options = {
|
||||
props: {},
|
||||
on: {
|
||||
pressEnter: () => this.$emit('pressEnter'),
|
||||
}
|
||||
}
|
||||
if (props != null) {
|
||||
Object.assign(options.props, props)
|
||||
}
|
||||
if (placeholder === undefined) {
|
||||
if (dictCode) {
|
||||
options.props['placeholder'] = `请选择${label}`
|
||||
} else {
|
||||
options.props['placeholder'] = `请输入${label}`
|
||||
}
|
||||
} else {
|
||||
options.props['placeholder'] = placeholder
|
||||
}
|
||||
|
||||
let input
|
||||
if (typeof customRender === 'function') {
|
||||
input = customRender.call(this, {key, options, queryParam: this.queryParam})
|
||||
} else if (dictCode) {
|
||||
input = <j-dict-select-tag {...options} vModel={this.queryParam[key]} dictCode={dictCode} style="width:180px;"/>
|
||||
} else {
|
||||
input = <a-input {...options} vModel={this.queryParam[key]}/>
|
||||
}
|
||||
return <a-form-item key={key} label={label}>{input}</a-form-item>
|
||||
})
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return <span>{this.renderQueryItem()}</span>
|
||||
},
|
||||
}
|
|
@ -11,12 +11,19 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import JDate from '@comp/jeecg/JDate'
|
||||
import JSelectBizComponent from './JSelectBizComponent'
|
||||
|
||||
export default {
|
||||
name: 'JSelectMultiUser',
|
||||
components: { JSelectBizComponent },
|
||||
props: ['value'],
|
||||
components: {JDate, JSelectBizComponent},
|
||||
props: {
|
||||
value: null, // any type
|
||||
queryConfig: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
url: { list: '/sys/user/list' },
|
||||
|
@ -33,12 +40,32 @@
|
|||
displayKey: 'realname',
|
||||
returnKeys: ['id', 'username'],
|
||||
queryParamText: '账号',
|
||||
}
|
||||
},
|
||||
// 多条件查询配置
|
||||
queryConfigDefault: [
|
||||
{
|
||||
key: 'sex',
|
||||
label: '性别',
|
||||
// 如果包含 dictCode,那么就会显示成下拉框
|
||||
dictCode: 'sex',
|
||||
},
|
||||
{
|
||||
key: 'birthday',
|
||||
label: '生日',
|
||||
placeholder: '请选择出生日期',
|
||||
// 如果想要使用局部注册的组件,就必须要使用箭头函数
|
||||
customRender: ({key, queryParam, options}) => {
|
||||
return <j-date {...options} vModel={queryParam[key]} style="width:180px;"/>
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
attrs() {
|
||||
return Object.assign(this.default, this.$attrs)
|
||||
return Object.assign(this.default, this.$attrs, {
|
||||
queryConfig: this.queryConfigDefault.concat(this.queryConfig)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<a-spin tip="Loading..." :spinning="false">
|
||||
<a-input-search style="margin-bottom: 1px" placeholder="请输入部门名称按回车进行搜索" @search="onSearch" />
|
||||
<a-tree
|
||||
class="my-dept-select-tree"
|
||||
checkable
|
||||
class="my-dept-select-tree"
|
||||
:treeData="treeData"
|
||||
:checkStrictly="true"
|
||||
@check="onCheck"
|
||||
|
@ -239,9 +239,10 @@
|
|||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 限制部门选择树高度,避免部门太多时点击确定不便
|
||||
.my-dept-select-tree{
|
||||
height: 350px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
// 限制部门选择树高度,避免部门太多时点击确定不便
|
||||
.my-dept-select-tree{
|
||||
height: 350px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -115,11 +115,11 @@
|
|||
}else if (this.linkList.indexOf(newRoute.fullPath) < 0) {
|
||||
this.linkList.push(newRoute.fullPath)
|
||||
this.pageList.push(Object.assign({},newRoute))
|
||||
// update-begin-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍
|
||||
if (newRoute.meta.keepAlive) {
|
||||
this.routeReload()
|
||||
}
|
||||
// update-end-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍
|
||||
// update-begin-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍 #842
|
||||
// if (newRoute.meta.keepAlive) {
|
||||
// this.routeReload()
|
||||
// }
|
||||
// update-end-author:sunjianlei date:20200103 for: 如果新增的页面配置了缓存路由,那么就强制刷新一遍 #842
|
||||
} else if (this.linkList.indexOf(newRoute.fullPath) >= 0) {
|
||||
let oldIndex = this.linkList.indexOf(newRoute.fullPath)
|
||||
let oldPositionRoute = this.pageList[oldIndex]
|
||||
|
@ -132,9 +132,8 @@
|
|||
// 【TESTA-523】修复:不允许重复跳转路由异常
|
||||
if (waitRouter.fullPath !== this.$route.fullPath) {
|
||||
this.$router.push(Object.assign({}, waitRouter))
|
||||
this.changeTitle(waitRouter.meta.title)
|
||||
}
|
||||
// 动态更改document.title
|
||||
this.changeTitle(waitRouter.meta.title)
|
||||
},
|
||||
'multipage': function(newVal) {
|
||||
if(this.reloadFlag){
|
||||
|
@ -309,8 +308,12 @@
|
|||
this.$store.dispatch(ToggleMultipage,true)
|
||||
this.reloadFlag = true
|
||||
})
|
||||
}
|
||||
},
|
||||
//update-end-author:taoyan date:20191008 for:路由刷新
|
||||
//新增一个返回方法
|
||||
excuteCallback(callback){
|
||||
callback()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
// base library
|
||||
import {
|
||||
ConfigProvider,
|
||||
Layout,
|
||||
Input,
|
||||
InputNumber,
|
||||
Button,
|
||||
Switch,
|
||||
Radio,
|
||||
Checkbox,
|
||||
Select,
|
||||
Card,
|
||||
Form,
|
||||
Row,
|
||||
Col,
|
||||
Modal,
|
||||
Table,
|
||||
Tabs,
|
||||
Icon,
|
||||
Badge,
|
||||
Popover,
|
||||
Dropdown,
|
||||
List,
|
||||
Avatar,
|
||||
Breadcrumb,
|
||||
Steps,
|
||||
Spin,
|
||||
Menu,
|
||||
Drawer,
|
||||
Tooltip,
|
||||
Alert,
|
||||
Tag,
|
||||
Divider,
|
||||
DatePicker,
|
||||
TimePicker,
|
||||
Upload,
|
||||
Progress,
|
||||
Skeleton,
|
||||
Popconfirm,
|
||||
PageHeader,
|
||||
Result,
|
||||
Statistic,
|
||||
Descriptions,
|
||||
message,
|
||||
notification,
|
||||
Empty,
|
||||
Tree,
|
||||
TreeSelect,
|
||||
Carousel,
|
||||
Pagination,
|
||||
} from 'ant-design-vue'
|
||||
import Viser from 'viser-vue'
|
||||
|
||||
Vue.use(ConfigProvider)
|
||||
Vue.use(Layout)
|
||||
Vue.use(Input)
|
||||
Vue.use(InputNumber)
|
||||
Vue.use(Button)
|
||||
Vue.use(Switch)
|
||||
Vue.use(Radio)
|
||||
Vue.use(Checkbox)
|
||||
Vue.use(Select)
|
||||
Vue.use(Card)
|
||||
Vue.use(Form)
|
||||
Vue.use(Row)
|
||||
Vue.use(Col)
|
||||
Vue.use(Modal)
|
||||
Vue.use(Table)
|
||||
Vue.use(Tabs)
|
||||
Vue.use(Icon)
|
||||
Vue.use(Badge)
|
||||
Vue.use(Popover)
|
||||
Vue.use(Dropdown)
|
||||
Vue.use(List)
|
||||
Vue.use(Avatar)
|
||||
Vue.use(Breadcrumb)
|
||||
Vue.use(Steps)
|
||||
Vue.use(Spin)
|
||||
Vue.use(Menu)
|
||||
Vue.use(Drawer)
|
||||
Vue.use(Tooltip)
|
||||
Vue.use(Alert)
|
||||
Vue.use(Tag)
|
||||
Vue.use(Divider)
|
||||
Vue.use(DatePicker)
|
||||
Vue.use(TimePicker)
|
||||
Vue.use(Upload)
|
||||
Vue.use(Progress)
|
||||
Vue.use(Skeleton)
|
||||
Vue.use(Popconfirm)
|
||||
Vue.use(PageHeader)
|
||||
Vue.use(Result)
|
||||
Vue.use(Statistic)
|
||||
Vue.use(Descriptions)
|
||||
Vue.use(Empty)
|
||||
Vue.use(Tree)
|
||||
Vue.use(TreeSelect)
|
||||
Vue.use(Carousel)
|
||||
Vue.use(Pagination)
|
||||
|
||||
Vue.prototype.$confirm = Modal.confirm
|
||||
Vue.prototype.$message = message
|
||||
Vue.prototype.$notification = notification
|
||||
Vue.prototype.$info = Modal.info
|
||||
Vue.prototype.$success = Modal.success
|
||||
Vue.prototype.$error = Modal.error
|
||||
Vue.prototype.$warning = Modal.warning
|
||||
|
||||
process.env.NODE_ENV !== 'production' && console.warn('[jeecg-boot-vue] NOTICE: Antd use lazy-load.')
|
|
@ -43,7 +43,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<template v-if="layoutMode === 'sidemenu'">
|
||||
<a-drawer
|
||||
v-if="device === 'mobile'"
|
||||
v-show="device === 'mobile'"
|
||||
:wrapClassName="'drawer-sider ' + navTheme"
|
||||
placement="left"
|
||||
@close="() => this.collapsed = false"
|
||||
|
@ -13,6 +13,7 @@
|
|||
>
|
||||
<side-menu
|
||||
mode="inline"
|
||||
v-if="device === 'mobile'"
|
||||
:menus="menus"
|
||||
@menuSelect="menuSelect"
|
||||
:theme="navTheme"
|
||||
|
@ -21,7 +22,7 @@
|
|||
</a-drawer>
|
||||
|
||||
<side-menu
|
||||
v-else
|
||||
v-if="device === 'desktop'"
|
||||
mode="inline"
|
||||
:menus="menus"
|
||||
@menuSelect="myMenuSelect"
|
||||
|
@ -343,7 +344,7 @@
|
|||
}
|
||||
|
||||
.anticon {
|
||||
color: white;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -359,7 +360,7 @@
|
|||
}
|
||||
|
||||
.anticon {
|
||||
color: black;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,8 +112,9 @@ export default {
|
|||
pageSize: (pagination && pagination.pageSize) ||
|
||||
this.localPagination.pageSize
|
||||
});
|
||||
|
||||
!r.totalCount && ['auto', false].includes(this.showPagination) && (this.localPagination = false)
|
||||
//update--begin--autor:wangshuai-----date:20200724------for:判断showPagination是否为false------
|
||||
(!this.showPagination || !r.totalCount && this.showPagination === 'auto') && (this.localPagination = false)
|
||||
//update--end--autor:wangshuai-----date:20200724------for:判断showPagination是否为false-----
|
||||
this.localDataSource = r.data; // 返回结果中的数组数据
|
||||
this.localLoading = false
|
||||
});
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
methods: {
|
||||
show(uname){
|
||||
if(!uname){
|
||||
this.$message.warning("当前系统无登陆用户!");
|
||||
this.$message.warning("当前系统无登录用户!");
|
||||
return
|
||||
}else{
|
||||
this.username = uname
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// src/icons.js
|
||||
|
||||
// export what you need
|
||||
export {
|
||||
default as SmileOutline
|
||||
} from '@ant-design/icons/lib/outline/SmileOutline';
|
||||
export {
|
||||
default as MehOutline
|
||||
} from '@ant-design/icons/lib/outline/MehOutline';
|
||||
|
||||
// export what antd other components need
|
||||
export {
|
||||
default as CloseOutline
|
||||
} from '@ant-design/icons/lib/outline/CloseOutline';
|
||||
// and other icons...
|
|
@ -6,7 +6,9 @@ import store from './store/'
|
|||
|
||||
import { VueAxios } from "@/utils/request"
|
||||
|
||||
import Antd from 'ant-design-vue'
|
||||
import Antd, { version } from 'ant-design-vue'
|
||||
console.log('ant-design-vue version:', version)
|
||||
|
||||
import Viser from 'viser-vue'
|
||||
import 'ant-design-vue/dist/antd.less'; // or 'ant-design-vue/dist/antd.less'
|
||||
|
||||
|
@ -20,6 +22,7 @@ import 'vue-photo-preview/dist/skin.css'
|
|||
require('@jeecg/antd-online-mini')
|
||||
require('@jeecg/antd-online-mini/dist/OnlineForm.css')
|
||||
|
||||
|
||||
import {
|
||||
ACCESS_TOKEN,
|
||||
DEFAULT_COLOR,
|
||||
|
@ -41,6 +44,8 @@ import vueBus from '@/utils/vueBus';
|
|||
import JeecgComponents from '@/components/jeecg/index'
|
||||
import '@/assets/less/JAreaLinkage.less'
|
||||
import VueAreaLinkage from 'vue-area-linkage'
|
||||
import '@/components/jeecg/JVxeTable/install'
|
||||
import '@/components/JVxeCells/install'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(Storage, config.storageOptions)
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
import { VALIDATE_FAILED, getRefPromise, validateFormAndTables} from '@/components/jeecg/JVxeTable/utils/vxeUtils.js'
|
||||
import { httpAction, getAction } from '@/api/manage'
|
||||
|
||||
export const JVxeTableMixin = {
|
||||
data() {
|
||||
return {
|
||||
title: '操作',
|
||||
visible: false,
|
||||
form: this.$form.createForm(this),
|
||||
confirmLoading: false,
|
||||
scrolling: true,
|
||||
model: {},
|
||||
labelCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 6 }
|
||||
},
|
||||
wrapperCol: {
|
||||
xs: { span: 24 },
|
||||
sm: { span: 18 }
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
/** 获取所有的JVxeTable实例 */
|
||||
getAllTable() {
|
||||
if (!(this.refKeys instanceof Array)) {
|
||||
throw this.throwNotArray('refKeys')
|
||||
}
|
||||
let values = this.refKeys.map(key => getRefPromise(this, key))
|
||||
return Promise.all(values)
|
||||
},
|
||||
|
||||
/** 遍历所有的JVxeTable实例 */
|
||||
eachAllTable(callback) {
|
||||
// 开始遍历
|
||||
this.getAllTable().then(tables => {
|
||||
tables.forEach((item, index) => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(item, index)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
/** 当点击新增按钮时调用此方法 */
|
||||
add() {
|
||||
if (typeof this.addBefore === 'function') this.addBefore()
|
||||
// 默认新增空数据
|
||||
let rowNum = this.addDefaultRowNum
|
||||
if (typeof rowNum !== 'number') {
|
||||
rowNum = 1
|
||||
console.warn('由于你没有在 data 中定义 addDefaultRowNum 或 addDefaultRowNum 不是数字,所以默认添加一条空数据,如果不想默认添加空数据,请将定义 addDefaultRowNum 为 0')
|
||||
}
|
||||
this.eachAllTable((item) => {
|
||||
item.addRows()
|
||||
//item.add(rowNum)
|
||||
})
|
||||
if (typeof this.addAfter === 'function') this.addAfter(this.model)
|
||||
this.edit({})
|
||||
},
|
||||
/** 当点击了编辑(修改)按钮时调用此方法 */
|
||||
edit(record) {
|
||||
if (typeof this.editBefore === 'function') this.editBefore(record)
|
||||
this.visible = true
|
||||
this.activeKey = this.refKeys[0]
|
||||
this.form.resetFields()
|
||||
this.model = Object.assign({}, record)
|
||||
if (typeof this.editAfter === 'function') this.editAfter(this.model)
|
||||
},
|
||||
/** 关闭弹窗,并将所有JVxeTable实例回归到初始状态 */
|
||||
close() {
|
||||
this.visible = false
|
||||
this.eachAllTable((item) => {
|
||||
item._remove()
|
||||
})
|
||||
this.$emit('close')
|
||||
},
|
||||
|
||||
/** 查询某个tab的数据 */
|
||||
requestSubTableData(url, params, tab, success) {
|
||||
tab.loading = true
|
||||
getAction(url, params).then(res => {
|
||||
let { result } = res
|
||||
let dataSource = []
|
||||
if (result) {
|
||||
if (Array.isArray(result)) {
|
||||
dataSource = result
|
||||
} else if (Array.isArray(result.records)) {
|
||||
dataSource = result.records
|
||||
}
|
||||
}
|
||||
tab.dataSource = dataSource
|
||||
typeof success === 'function' ? success(res) : ''
|
||||
}).finally(() => {
|
||||
tab.loading = false
|
||||
})
|
||||
},
|
||||
/** 发起请求,自动判断是执行新增还是修改操作 */
|
||||
request(formData) {
|
||||
let url = this.url.add, method = 'post'
|
||||
if (this.model.id) {
|
||||
url = this.url.edit
|
||||
method = 'put'
|
||||
}
|
||||
this.confirmLoading = true
|
||||
console.log("formData===>",formData);
|
||||
httpAction(url, formData, method).then((res) => {
|
||||
if (res.success) {
|
||||
this.$message.success(res.message)
|
||||
this.$emit('ok')
|
||||
this.close()
|
||||
} else {
|
||||
this.$message.warning(res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.confirmLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
/* --- handle 事件 --- */
|
||||
|
||||
/** ATab 选项卡切换事件 */
|
||||
handleChangeTabs(key) {
|
||||
// 自动重置scrollTop状态,防止出现白屏
|
||||
getRefPromise(this, key).then(vxeTable => {
|
||||
vxeTable.resetScrollTop()
|
||||
})
|
||||
},
|
||||
/** 关闭按钮点击事件 */
|
||||
handleCancel() {
|
||||
this.close()
|
||||
},
|
||||
/** 确定按钮点击事件 */
|
||||
handleOk() {
|
||||
/** 触发表单验证 */
|
||||
this.getAllTable().then(tables => {
|
||||
/** 一次性验证主表和所有的次表 */
|
||||
return validateFormAndTables(this.form, tables)
|
||||
}).then(allValues => {
|
||||
if (typeof this.classifyIntoFormData !== 'function') {
|
||||
throw this.throwNotFunction('classifyIntoFormData')
|
||||
}
|
||||
let formData = this.classifyIntoFormData(allValues)
|
||||
// 发起请求
|
||||
return this.request(formData)
|
||||
}).catch(e => {
|
||||
if (e.error === VALIDATE_NO_PASSED) {
|
||||
// 如果有未通过表单验证的子表,就自动跳转到它所在的tab
|
||||
this.activeKey = e.index == null ? this.activeKey : this.refKeys[e.index]
|
||||
} else {
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/* --- throw --- */
|
||||
|
||||
/** not a function */
|
||||
throwNotFunction(name) {
|
||||
return `${name} 未定义或不是一个函数`
|
||||
},
|
||||
|
||||
/** not a array */
|
||||
throwNotArray(name) {
|
||||
return `${name} 未定义或不是一个数组`
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import { filterMultiDictText } from '@/components/dict/JDictSelectUtil'
|
||||
|
||||
export const HrefJump = {
|
||||
data() {
|
||||
return {
|
||||
fieldHrefSlots: [],
|
||||
hrefComponent: {
|
||||
model: {
|
||||
title: '',
|
||||
width: '100%',
|
||||
visible: false,
|
||||
destroyOnClose: true,
|
||||
style: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
margin: 0,
|
||||
padding: 0
|
||||
},
|
||||
bodyStyle: { padding: '8px', height: 'calc(100vh - 108px)', overflow: 'auto', overflowX: 'hidden' },
|
||||
// 隐藏掉取消按钮
|
||||
cancelButtonProps: { style: { display: 'none' } }
|
||||
},
|
||||
on: {
|
||||
ok: () => this.hrefComponent.model.visible = false,
|
||||
cancel: () => this.hrefComponent.model.visible = false
|
||||
},
|
||||
is: null,
|
||||
params: {},
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理接收href参数
|
||||
handleAcceptHrefParams(){
|
||||
this.acceptHrefParams={}
|
||||
let hrefparam = this.$route.query;
|
||||
if(hrefparam){
|
||||
this.acceptHrefParams = {...hrefparam}
|
||||
}
|
||||
},
|
||||
//支持链接href跳转
|
||||
handleClickFieldHref(field, record) {
|
||||
let href = field.href
|
||||
let urlPattern = /(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&%\$#_]*)?/
|
||||
let compPattern = /\.vue(\?.*)?$/
|
||||
if (typeof href === 'string') {
|
||||
href = href.trim().replace(/\${([^}]+)?}/g, (s1, s2) => record[s2])
|
||||
if (urlPattern.test(href)) {
|
||||
window.open(href, '_blank')
|
||||
} else if (compPattern.test(href)) {
|
||||
this.openHrefCompModal(href)
|
||||
} else {
|
||||
this.$router.push(href)
|
||||
}
|
||||
}
|
||||
},
|
||||
openHrefCompModal(href) {
|
||||
// 解析 href 参数
|
||||
let index = href.indexOf('?')
|
||||
let path = href
|
||||
if (index !== -1) {
|
||||
path = href.substring(0, index)
|
||||
let paramString = href.substring(index + 1, href.length)
|
||||
let paramArray = paramString.split('&')
|
||||
let params = {}
|
||||
paramArray.forEach(paramObject => {
|
||||
let paramItem = paramObject.split('=')
|
||||
params[paramItem[0]] = paramItem[1]
|
||||
})
|
||||
this.hrefComponent.params = params
|
||||
} else {
|
||||
this.hrefComponent.params = {}
|
||||
}
|
||||
this.hrefComponent.model.visible = true
|
||||
this.hrefComponent.model.title = '操作'
|
||||
this.hrefComponent.is = () => import('@/views/' + (path.startsWith('/') ? path.slice(1) : path))
|
||||
},
|
||||
/** 处理列中的 href 跳转和 dict 字典,使两者可以兼容存在 */
|
||||
handleColumnHrefAndDict(column = {}, fieldHrefSlotKeysMap = {}) {
|
||||
let { customRender, hrefSlotName } = column
|
||||
if (!hrefSlotName && (column.scopedSlots && column.scopedSlots.customRender)) {
|
||||
//hrefSlotName = column.scopedSlots.customRender
|
||||
}
|
||||
// 如果 customRender 有值则代表使用了字典
|
||||
// 如果 hrefSlotName 有值则代表使用了href跳转
|
||||
// 两者可以兼容。兼容的具体思路为:先获取到字典替换的值,再添加href链接跳转
|
||||
if (customRender || hrefSlotName) {
|
||||
let dictCode = customRender
|
||||
let replaceFlag = '_replace_text_'
|
||||
column.customRender = (text, record) => {
|
||||
let value = text
|
||||
// 如果 dictCode 有值,就进行字典转换
|
||||
if (dictCode) {
|
||||
if (dictCode.startsWith(replaceFlag)) {
|
||||
let textFieldName = dictCode.replace(replaceFlag, '')
|
||||
value = record[textFieldName]
|
||||
} else {
|
||||
value = filterMultiDictText(this.dictOptions[dictCode], text)
|
||||
}
|
||||
}
|
||||
// 如果 hrefSlotName 有值,就生成一个 a 标签,包裹住字典替换后(或原生)的值
|
||||
if (hrefSlotName) {
|
||||
let field = fieldHrefSlotKeysMap[hrefSlotName]
|
||||
if (field) {
|
||||
// 此处为 JSX 语法
|
||||
return (<a onClick={() => this.handleClickFieldHref(field, record)}>{value}</a>)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
import store from '@/store/'
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types'
|
||||
import Vue from 'vue'
|
||||
|
||||
export const WebsocketMixin = {
|
||||
mounted() {
|
||||
this.initWebSocket();
|
||||
|
@ -9,6 +12,7 @@ export const WebsocketMixin = {
|
|||
},
|
||||
methods:{
|
||||
initWebSocket: function () {
|
||||
let token = Vue.ls.get(ACCESS_TOKEN)
|
||||
console.log("------------WebSocket连接成功");
|
||||
// WebSocket与普通的请求所用协议有所不同,ws等同于http,wss等同于https
|
||||
var userId = store.getters.userInfo.id;
|
||||
|
@ -18,7 +22,7 @@ export const WebsocketMixin = {
|
|||
if(!this.socketUrl.endsWith('/')){
|
||||
this.socketUrl = this.socketUrl + '/'
|
||||
}
|
||||
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://") + this.socketUrl + userId;
|
||||
var url = window._CONFIG['domianURL'].replace("https://","wss://").replace("http://","ws://") + this.socketUrl + userId + "/" + token;
|
||||
this.websock = new WebSocket(url);
|
||||
this.websock.onopen = this.websocketOnopen;
|
||||
this.websock.onerror = this.websocketOnerror;
|
||||
|
|
|
@ -12,6 +12,7 @@ const getters = {
|
|||
permissionList: state => state.user.permissionList,
|
||||
userInfo: state => {state.user.info = Vue.ls.get(USER_INFO); return state.user.info},
|
||||
addRouters: state => state.permission.addRouters,
|
||||
onlAuthFields: state => {return state.online.authFields },
|
||||
enhanceJs:(state) => (code) => {
|
||||
state.enhance.enhanceJs[code] = Vue.ls.get(ENHANCE_PRE+code);
|
||||
return state.enhance.enhanceJs[code]
|
||||
|
@ -19,4 +20,4 @@ const getters = {
|
|||
|
||||
}
|
||||
|
||||
export default getters
|
||||
export default getters
|
||||
|
|
|
@ -5,6 +5,7 @@ import app from './modules/app'
|
|||
import user from './modules/user'
|
||||
import permission from './modules/permission'
|
||||
import enhance from './modules/enhance'
|
||||
import online from './modules/online'
|
||||
import getters from './getters'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
@ -14,7 +15,8 @@ export default new Vuex.Store({
|
|||
app,
|
||||
user,
|
||||
permission,
|
||||
enhance
|
||||
enhance,
|
||||
online
|
||||
},
|
||||
state: {
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import Vue from 'vue'
|
||||
import { ONL_AUTH_FIELDS } from "@/store/mutation-types"
|
||||
import { getAction } from '@/api/manage'
|
||||
|
||||
|
||||
const online = {
|
||||
state: {
|
||||
//存储对象属性 value,text
|
||||
authFields: [],
|
||||
},
|
||||
mutations: {
|
||||
SET_AUTHFIELDS: (state, fields) => {
|
||||
console.log('fields',fields)
|
||||
Vue.set(state, 'authFields', fields)
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
// TODO 如果没找到可以尝试请求一下
|
||||
xxxxxx({ commit }, userInfo) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default online
|
|
@ -16,8 +16,9 @@ export const SYS_BUTTON_AUTH = 'SYS_BUTTON_AUTH'
|
|||
export const ENCRYPTED_STRING = 'ENCRYPTED_STRING'
|
||||
export const ENHANCE_PRE = 'enhance_'
|
||||
export const UI_CACHE_DB_DICT_DATA = 'UI_CACHE_DB_DICT_DATA'
|
||||
export const INDEX_MAIN_PAGE_PATH = "/dashboard/analysis"
|
||||
export const INDEX_MAIN_PAGE_PATH = '/dashboard/analysis'
|
||||
export const TENANT_ID = 'TENANT_ID'
|
||||
export const ONL_AUTH_FIELDS = 'ONL_AUTH_FIELDS'
|
||||
|
||||
export const CONTENT_WIDTH_TYPE = {
|
||||
Fluid: 'Fluid',
|
||||
|
|
|
@ -174,7 +174,10 @@ function hasColoum(item,authList){
|
|||
|
||||
//权限无效时不做控制,有效时控制,只能控制 显示不显示
|
||||
//根据授权码前缀获取未授权的列信息
|
||||
function getNoAuthCols(pre){
|
||||
export function getNoAuthCols(pre){
|
||||
if(!pre || pre.length==0){
|
||||
return []
|
||||
}
|
||||
let permissionList = [];
|
||||
let allPermissionList = [];
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import router from '@/router'
|
||||
import { getAction } from '@api/manage'
|
||||
|
||||
/** 表单设计器路由类型 */
|
||||
export const DESFORM_ROUTE_TYPE = {
|
||||
/** 跳转到表单 */
|
||||
form: '1',
|
||||
/** 跳转到菜单 */
|
||||
menu: '2',
|
||||
/** 跳转到外部链接 */
|
||||
href: '3',
|
||||
}
|
||||
/** 表单设计器路由跳转带过来的ID名字 */
|
||||
export const DESFORM_ROUTE_DATA_ID = 'routeDataId'
|
||||
|
||||
/**
|
||||
* 获取当前路由或指定路由的 routeDataId
|
||||
*
|
||||
* @param $route 路由对象,默认为当前路由
|
||||
*/
|
||||
export function getDesformRouteDataId($route) {
|
||||
if (arguments.length === 0) {
|
||||
$route = router.currentRoute
|
||||
}
|
||||
if ($route) {
|
||||
return $route.query[DESFORM_ROUTE_DATA_ID]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路由跳转带过来的ID来获取表单设计器的数据
|
||||
* @param param 表单设计器路由跳转带过来的ID(可以直接传id字符串,也可以传 this.$route,自动从$route里获取id,如果不传就获取当前路由的参数)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function getDesformDataByRouteDataId(param) {
|
||||
if (!param) {
|
||||
param = router.currentRoute
|
||||
}
|
||||
let id
|
||||
if (typeof param == 'string') {
|
||||
id = param
|
||||
} else if (typeof param.query === 'object') {
|
||||
id = getDesformRouteDataId(param)
|
||||
if (!id) {
|
||||
// 当前$route.query里没有带表单设计器路由ID,直接返回null
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
throw new Error('传递的参数不能识别,可以直接传id字符串,也可以传 this.$route,自动从$route里获取id,如果不传就获取当前路由的参数')
|
||||
}
|
||||
let url = `/desform/data/queryById?id=${id}`
|
||||
let { success, result, message } = await getAction(url)
|
||||
if (success) {
|
||||
result.desformDataJson = JSON.parse(result.desformDataJson)
|
||||
return result
|
||||
} else {
|
||||
throw new Error('表单设计器路由数据获取失败:' + message)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* 省市区联动组件通用工具类。
|
||||
* 列表翻译、组件反推、表单设计器组件反推等功能都可以用该工具类实现。
|
||||
*
|
||||
* 1. leafName字段的意义:
|
||||
* AntdvUI和ElementUI的叶级节点名字是不一样的,
|
||||
* AntdvUI的名字是 isLeaf,ElementUI是 leaf,
|
||||
* 默认是AntdvUI的名字,在表单设计器那边需要手动 setLeafName
|
||||
*/
|
||||
|
||||
import { pcaa } from 'area-data'
|
||||
|
||||
export { pcaa }
|
||||
|
||||
/** 根节点Code = 86 */
|
||||
export const ROOT_CODE = '86'
|
||||
|
||||
/** 叶级节点的名字 */
|
||||
let leafName = 'isLeaf'
|
||||
|
||||
/**
|
||||
* set leafName
|
||||
* @param $leafName
|
||||
*/
|
||||
export function setLeafName($leafName = 'isLeaf') {
|
||||
leafName = $leafName
|
||||
}
|
||||
|
||||
/**
|
||||
* 将地区数据转换成下拉框等组件可识别的Options数组
|
||||
* @param data 地区data
|
||||
* @param labelName label标签名字
|
||||
* @return
|
||||
*/
|
||||
export function transToOptions(data, labelName = 'label') {
|
||||
if (data) {
|
||||
return Object.keys(data).map(key => ({ value: key, [labelName]: data[key] }))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子级Data对象
|
||||
*
|
||||
* @param code 父级地区Code
|
||||
*/
|
||||
export function getChildrenDataByCode(code) {
|
||||
return pcaa[code]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取子级Options对象
|
||||
*
|
||||
* @param code 父级地区Code
|
||||
* @return {Array} 返回的值一定是一个数组,如果数组length===0,则代表没有子级
|
||||
*/
|
||||
export function getChildrenOptionsByCode(code) {
|
||||
let options = []
|
||||
let data = getChildrenDataByCode(code)
|
||||
if (data) {
|
||||
for (let key in data) {
|
||||
if (data.hasOwnProperty(key)) {
|
||||
options.push({ value: key, label: data[key], })
|
||||
}
|
||||
}
|
||||
return options
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取兄弟Data对象
|
||||
*
|
||||
* @param code 地区Code
|
||||
*/
|
||||
export function getSiblingsDataByCode(code) {
|
||||
if (typeof code === 'string' && code.length === 6) {
|
||||
// 父级节点Code
|
||||
let parentCode = `${code.substring(0, 4)}00`
|
||||
return getChildrenDataByCode(parentCode)
|
||||
} else {
|
||||
console.warn('[getSiblingsByCode]: code不合法')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应的 Label
|
||||
*
|
||||
* @param code 地区Code
|
||||
*/
|
||||
export function getLabelByCode(code) {
|
||||
if (code) {
|
||||
// 获取当前code所有的兄弟节点
|
||||
let siblingsData = getSiblingsDataByCode(code)
|
||||
// 然后取出自己的值
|
||||
return siblingsData[code]
|
||||
} else {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有的 Label,包括自身和所有父级
|
||||
*
|
||||
* @param code 地区Code
|
||||
* @param joinText 合并文本
|
||||
*/
|
||||
export function getAllLabelByCode(code, joinText = ' / ') {
|
||||
if (code) {
|
||||
let { labels } = getAllParentByCode(code)
|
||||
return labels.join(joinText)
|
||||
} else {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过子级 code 反推所有父级的 code
|
||||
*
|
||||
* @param code 子级地区Code
|
||||
* @returns {Object} options: 所有父级的选项;codes: 所有父级的code;labels: 所有父级的显示名
|
||||
*/
|
||||
export function getAllParentByCode(code) {
|
||||
code = (typeof code === 'string' ? code : '').trim()
|
||||
if (code.length === 0) {
|
||||
return { options: [], codes: [], labels: [] }
|
||||
}
|
||||
// 获取第一级数据
|
||||
let rootOptions = getChildrenOptionsByCode(ROOT_CODE)
|
||||
hasChildren(rootOptions)
|
||||
// 父级code数组,code长度
|
||||
let parentCodes = [code], length = code.length
|
||||
// 父级label数组
|
||||
let parentLabels = [getLabelByCode(code)]
|
||||
// 级别,位数,是否继续循环
|
||||
let level = 1, num = 2, flag = true
|
||||
|
||||
let options = rootOptions
|
||||
do {
|
||||
let endIndex = num * level++
|
||||
// 末尾补零个数
|
||||
let zeroPadding = [...new Array(length - endIndex)].map(i => '0').join('')
|
||||
// 裁剪并补零(获取上级的方式就是将当前code的后两位变成 00)
|
||||
let parentCode = code.substring(0, endIndex) + zeroPadding
|
||||
// 是否找到在选项中的位置
|
||||
let findIt = false
|
||||
for (let option of options) {
|
||||
if (option.value === parentCode) {
|
||||
if (option[leafName]) {
|
||||
flag = false
|
||||
} else {
|
||||
let children = getChildrenOptionsByCode(option.value)
|
||||
hasChildren(children)
|
||||
option.children = children
|
||||
options = children
|
||||
parentCodes.splice(parentCodes.length - 1, 0, option.value)
|
||||
parentLabels.splice(parentLabels.length - 1, 0, option.label)
|
||||
}
|
||||
findIt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (findIt) {
|
||||
findIt = false
|
||||
} else {
|
||||
flag = false
|
||||
}
|
||||
} while (flag)
|
||||
return { options: rootOptions, codes: parentCodes, labels: parentLabels }
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断所有的项是否有子节点
|
||||
*
|
||||
* @param options
|
||||
*/
|
||||
export function hasChildren(options) {
|
||||
options.forEach(option => {
|
||||
option[leafName] = getChildrenOptionsByCode(option.value).length === 0
|
||||
})
|
||||
}
|
|
@ -109,7 +109,21 @@ export function filterGlobalPermission(el, binding, vnode) {
|
|||
let permissions = [];
|
||||
for (let item of permissionList) {
|
||||
if(item.type != '2'){
|
||||
permissions.push(item.action);
|
||||
//update--begin--autor:wangshuai-----date:20200729------for:按钮权限,授权标识的提示信息是多个用逗号分隔逻辑处理 gitee#I1OUGU-------
|
||||
if(item.action){
|
||||
if(item.action.includes(",")){
|
||||
let split = item.action.split(",")
|
||||
for (let i = 0; i <split.length ; i++) {
|
||||
if(!split[i] ||split[i].length==0){
|
||||
continue;
|
||||
}
|
||||
permissions.push(split[i]);
|
||||
}
|
||||
}else{
|
||||
permissions.push(item.action);
|
||||
}
|
||||
}
|
||||
//update--end--autor:wangshuai-----date:20200729------for:按钮权限,授权标识的提示信息是多个用逗号分隔逻辑处理 gitee#I1OUGU------
|
||||
}
|
||||
}
|
||||
if (!permissions.includes(binding.value)) {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 功能模块
|
||||
* @type {{LEAVE_EARLY: {text: string, value: string}, LATE: {text: string, value: string}, ABSENT: {text: string, value: string}, NO_SIGN: {text: string, value: string}, NORMAL: {text: string, value: string}}}
|
||||
*/
|
||||
export const FunctionEnum = {
|
||||
EoaCmsBanner: { path: 'modules/eoa/cmsoa/modules/EoaCmsBanner', formData: 'carouselImg' },
|
||||
EoaCmsNewsInfo: { path: 'modules/eoa/cmsoa/modules/EoaCmsNewsInfo', formData: 'newsInfo' },
|
||||
EoaCmsRuleInfo: { path: 'modules/eoa/cmsoa/modules/EoaCmsRuleInfo', formData: 'ruleDownInfo' },
|
||||
EoaCmsSignNews: { path: 'modules/eoa/cmsoa/modules/EoaCmsSignNews', formData: 'signNews' },
|
||||
EoaCmsUserNotice: { path: 'modules/eoa/cmsoa/modules/EoaCmsUserNotice', formData: 'userNotice' },
|
||||
EoaCmsPlan: { path: 'modules/eoa/cmsoa/modules/EoaCmsPlan', formData: '' },
|
||||
EoaCmsLink: { path: 'modules/eoa/cmsoa/modules/EoaCmsLink', formData: '' },
|
||||
EoaCmsCommUse:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsCommUse', formData: '' },
|
||||
EoaCmsMyProcess:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsMyProcess', formData: '' },
|
||||
EoaCmsApplyProcess:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsApplyProcess', formData: '' },
|
||||
EoaCmsProcessNotice:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsProcessNotice', formData: '' },
|
||||
EoaCmsProcessChatData:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsProcessChatData', formData: '' },
|
||||
EoaCmsProcessTypeChat:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsProcessTypeChat', formData: '' },
|
||||
EoaCmsEmail:{ path: 'modules/eoa/cmsbpm/modules/EoaCmsEmail', formData: '' },
|
||||
}
|
|
@ -32,9 +32,9 @@ const err = (error) => {
|
|||
notification.error({ message: '系统提示', description: '拒绝访问',duration: 4})
|
||||
break
|
||||
case 500:
|
||||
let path = window.location.href
|
||||
let path = window.location.href
|
||||
//notification.error({ message: '系统提示', description:'Token失效,请重新登录!',duration: 4})
|
||||
if(token && data.message=="Token失效,请重新登录" && path.indexOf('/user/login') < 0){
|
||||
if(token && data.message.includes("Token失效") && path.indexOf('/user/login') < 0){
|
||||
// update-begin- --- author:scott ------ date:20190225 ---- for:Token失效采用弹框模式,不直接跳转----
|
||||
// store.dispatch('Logout').then(() => {
|
||||
// window.location.reload()
|
||||
|
@ -48,7 +48,6 @@ const err = (error) => {
|
|||
store.dispatch('Logout').then(() => {
|
||||
Vue.ls.remove(ACCESS_TOKEN)
|
||||
try {
|
||||
let path=that.$route.path;
|
||||
if(path.indexOf('/user/login')==-1){
|
||||
window.location.reload()
|
||||
}
|
||||
|
|
|
@ -458,7 +458,7 @@ export function simpleDebounce(fn, delay = 100) {
|
|||
clearTimeout(timer)
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(null, args)
|
||||
fn.apply(this, args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
@ -527,4 +527,14 @@ export function getVmParentByName(vm, name) {
|
|||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 使一个值永远不会为(null | undefined)
|
||||
*
|
||||
* @param value 要处理的值
|
||||
* @param def 默认值,如果value为(null | undefined)则返回的默认值,可不传,默认为''
|
||||
*/
|
||||
export function neverNull(value, def) {
|
||||
return value == null ? (neverNull(def, '')) : value
|
||||
}
|
|
@ -38,7 +38,7 @@
|
|||
<a-input placeholder="h3gSbecd"/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="登陆密码"
|
||||
label="登录密码"
|
||||
:required="false"
|
||||
>
|
||||
<a-input placeholder="密码"/>
|
||||
|
|
|
@ -5,44 +5,39 @@
|
|||
<div class="account-settings-info-left">
|
||||
<a-menu
|
||||
:mode="device == 'mobile' ? 'horizontal' : 'inline'"
|
||||
:default-selected-keys="['settings']"
|
||||
:style="{ border: '0', width: device == 'mobile' ? '560px' : 'auto'}"
|
||||
:defaultSelectedKeys="defaultSelectedKeys"
|
||||
type="inner"
|
||||
@openChange="onOpenChange"
|
||||
v-model="selectedKeys"
|
||||
>
|
||||
<a-menu-item key="/account/settings/BaseSetting">
|
||||
<router-link :to="{ name: 'account-settings-base' }">
|
||||
<a-menu-item key="settings">
|
||||
<a @click="settingsClick()">
|
||||
基本设置
|
||||
</router-link>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/account/settings/security">
|
||||
<router-link :to="{ name: 'account-settings-security' }">
|
||||
安全设置
|
||||
</router-link>
|
||||
<a-menu-item key="security">
|
||||
<a @click="securityClick()">安全设置</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/account/settings/custom">
|
||||
<router-link :to="{ name: 'account-settings-custom' }">
|
||||
个性化
|
||||
</router-link>
|
||||
<a-menu-item key="custom">
|
||||
<a @click="customClick()"> 个性化</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/account/settings/binding">
|
||||
<router-link :to="{ name: 'account-settings-binding' }">
|
||||
账户绑定
|
||||
</router-link>
|
||||
<a-menu-item key="binding">
|
||||
<a @click="bindingClick()">账户绑定</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/account/settings/notification">
|
||||
<router-link :to="{ name: 'account-settings-notification' }">
|
||||
新消息通知
|
||||
</router-link>
|
||||
<a-menu-item key="notification">
|
||||
<a @click="notificationClick()">新消息通知</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div class="account-settings-info-right">
|
||||
<div class="account-settings-info-title">
|
||||
<span>{{ $route.meta.title }}</span>
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<route-view></route-view>
|
||||
<security ref="security" v-if="security"></security>
|
||||
<base-setting ref="baseSetting" v-if="baseSetting"></base-setting>
|
||||
<custom ref="custom" v-if="custom"></custom>
|
||||
<notification ref="notification" v-if="notification"></notification>
|
||||
<binding ref="binding" v-if="binding"></binding>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
@ -53,11 +48,20 @@
|
|||
import PageLayout from '@/components/page/PageLayout'
|
||||
import RouteView from "@/components/layouts/RouteView"
|
||||
import { mixinDevice } from '@/utils/mixin.js'
|
||||
|
||||
import security from './Security'
|
||||
import baseSetting from './BaseSetting'
|
||||
import custom from './Custom'
|
||||
import notification from './Notification'
|
||||
import binding from './Binding'
|
||||
export default {
|
||||
components: {
|
||||
RouteView,
|
||||
PageLayout
|
||||
PageLayout,
|
||||
security,
|
||||
baseSetting,
|
||||
custom,
|
||||
notification,
|
||||
binding
|
||||
},
|
||||
mixins: [mixinDevice],
|
||||
data () {
|
||||
|
@ -86,7 +90,13 @@
|
|||
fixedNumber: [1, 1]
|
||||
},
|
||||
|
||||
pageTitle: ''
|
||||
pageTitle: '',
|
||||
title:"基本设置",
|
||||
security:false,
|
||||
baseSetting:true,
|
||||
custom:false,
|
||||
notification:false,
|
||||
binding:false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -95,17 +105,6 @@
|
|||
mounted(){
|
||||
this.mainInfoHeight = (window.innerHeight-285)+"px";
|
||||
},
|
||||
computed: {
|
||||
/**
|
||||
* 根据路由计算当前选中菜单
|
||||
*
|
||||
* @author: tanpenggood
|
||||
* @date: 2020/9/7 18:50
|
||||
*/
|
||||
selectedKeys() {
|
||||
return [this.$route.path]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpenChange (openKeys) {
|
||||
this.openKeys = openKeys
|
||||
|
@ -113,7 +112,49 @@
|
|||
updateMenu () {
|
||||
let routes = this.$route.matched.concat()
|
||||
this.defaultSelectedKeys = [ routes.pop().path ]
|
||||
},
|
||||
//update-begin--Author:wangshuai Date:20200729 for:聚合路由错误 issues#1441--------------------
|
||||
settingsClick(){
|
||||
this.security=false
|
||||
this.custom=false
|
||||
this.notification=false
|
||||
this.binding=false
|
||||
this.baseSetting=true
|
||||
this.title="基本设置"
|
||||
},
|
||||
securityClick(){
|
||||
this.baseSetting=false
|
||||
this.custom=false;
|
||||
this.notification=false
|
||||
this.binding=false
|
||||
this.security=true
|
||||
this.title="安全设置"
|
||||
},
|
||||
notificationClick(){
|
||||
this.security=false
|
||||
this.custom=false
|
||||
this.baseSetting=false
|
||||
this.binding=false
|
||||
this.notification=true
|
||||
this.title="新消息通知"
|
||||
},
|
||||
bindingClick(){
|
||||
this.security=false
|
||||
this.baseSetting=false
|
||||
this.notification=false;
|
||||
this.custom=false;
|
||||
this.binding=true
|
||||
this.title="账号绑定"
|
||||
},
|
||||
customClick(){
|
||||
this.security=false
|
||||
this.baseSetting=false
|
||||
this.notification=false;
|
||||
this.binding=false
|
||||
this.custom=true;
|
||||
this.title="个性化"
|
||||
}
|
||||
//update-end--Author:wangshuai Date:20200729 for:聚合路由错误 issues#1441--------------------
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<a-card :bordered="false">
|
||||
|
||||
<a-tabs>
|
||||
<a-tab-pane tab="基础示例" key="1" forceRender>
|
||||
<j-vxe-demo1/>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane tab="高级示例" key="2" forceRender>
|
||||
<j-vxe-demo2/>
|
||||
</a-tab-pane>
|
||||
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JVxeDemo1 from '@views/jeecg/JVxeDemo/JVxeDemo1'
|
||||
import JVxeDemo2 from '@views/jeecg/JVxeDemo/JVxeDemo2'
|
||||
|
||||
export default {
|
||||
name: 'JVXETableDemo',
|
||||
components: {JVxeDemo2, JVxeDemo1},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,304 @@
|
|||
<template>
|
||||
<j-vxe-table
|
||||
ref="vTable"
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
drag-sort
|
||||
keep-source
|
||||
:height="580"
|
||||
:loading="loading"
|
||||
:dataSource="dataSource"
|
||||
:columns="columns"
|
||||
style="margin-top: 8px;"
|
||||
@valueChange="handleValueChange"
|
||||
>
|
||||
|
||||
<template v-slot:toolbarSuffix>
|
||||
<a-button @click="handleTableCheck">表单验证</a-button>
|
||||
<a-tooltip placement="top" title="获取值,忽略表单验证" :autoAdjustOverflow="true">
|
||||
<a-button @click="handleTableGet">获取值</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="top" title="模拟加载1000条数据" :autoAdjustOverflow="true">
|
||||
<a-button @click="handleTableSet">设置值</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-slot:action="props">
|
||||
<a @click="handleCK(props)">查看</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm title="确定删除吗?" @confirm="handleDL(props)">
|
||||
<a>删除</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
|
||||
</j-vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { pushIfNotExist, randomNumber, randomUUID } from '@/utils/util'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDemo1',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
columns: [
|
||||
{
|
||||
title: '不可编辑',
|
||||
key: 'normal',
|
||||
type: JVXETypes.normal,
|
||||
width: '180px',
|
||||
fixed: 'left',
|
||||
defaultValue: 'normal-new',
|
||||
},
|
||||
{
|
||||
title: '单行文本',
|
||||
key: 'input',
|
||||
type: JVXETypes.input,
|
||||
width: '180px',
|
||||
defaultValue: '',
|
||||
placeholder: '请输入${title}',
|
||||
validateRules: [
|
||||
{
|
||||
required: true, // 必填
|
||||
message: '请输入${title}' // 显示的文本
|
||||
},
|
||||
{
|
||||
pattern: /^[a-z|A-Z][a-z|A-Z\d_-]*$/, // 正则
|
||||
message: '${title}必须以字母开头,可包含数字、下划线、横杠'
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
message: '${title}不能重复'
|
||||
},
|
||||
{
|
||||
handler({cellValue, row, column}, callback, target) {
|
||||
// cellValue 当前校验的值
|
||||
// callback(flag, message) 方法必须执行且只能执行一次
|
||||
// flag = 是否通过了校验,不填写或者填写 null 代表不进行任何操作
|
||||
// message = 提示的类型,默认使用配置的 message
|
||||
// target 行编辑的实例对象
|
||||
if (cellValue === 'abc') {
|
||||
callback(false, '${title}不能是abc') // false = 未通过校验
|
||||
} else {
|
||||
callback(true) // true = 通过验证
|
||||
}
|
||||
},
|
||||
message: '${title}默认提示'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '多行文本',
|
||||
key: 'textarea',
|
||||
type: JVXETypes.textarea,
|
||||
width: '200px',
|
||||
},
|
||||
{
|
||||
title: '数字',
|
||||
key: 'number',
|
||||
type: JVXETypes.inputNumber,
|
||||
width: '80px',
|
||||
defaultValue: 32,
|
||||
// 【统计列】sum = 求和、average = 平均值
|
||||
statistics: ['sum', 'average'],
|
||||
},
|
||||
{
|
||||
title: '下拉框',
|
||||
key: 'select',
|
||||
type: JVXETypes.select,
|
||||
width: '180px',
|
||||
// 下拉选项
|
||||
options: [
|
||||
{title: 'String', value: 'string'},
|
||||
{title: 'Integer', value: 'int'},
|
||||
{title: 'Double', value: 'double'},
|
||||
{title: 'Boolean', value: 'boolean'}
|
||||
],
|
||||
allowInput: true,
|
||||
placeholder: '请选择'
|
||||
},
|
||||
{
|
||||
title: '下拉框_字典',
|
||||
key: 'select_dict',
|
||||
type: JVXETypes.select,
|
||||
width: '180px',
|
||||
options: [],
|
||||
dictCode: 'sex',
|
||||
placeholder: '请选择',
|
||||
},
|
||||
{
|
||||
title: '下拉框_多选',
|
||||
key: 'select_multiple',
|
||||
type: JVXETypes.selectMultiple,
|
||||
width: '180px',
|
||||
options: [
|
||||
{title: 'String', value: 'string'},
|
||||
{title: 'Integer', value: 'int'},
|
||||
{title: 'Double', value: 'double'},
|
||||
{title: 'Boolean', value: 'boolean'}
|
||||
],
|
||||
defaultValue: ['int', 'boolean'], // 多个默认项
|
||||
// defaultValue: 'string,double,int', // 也可使用这种方式
|
||||
placeholder: '多选',
|
||||
},
|
||||
|
||||
{
|
||||
title: '下拉框_搜索',
|
||||
key: 'select_search',
|
||||
type: JVXETypes.selectSearch,
|
||||
width: '180px',
|
||||
options: [
|
||||
{title: 'String', value: 'string'},
|
||||
{title: 'Integer', value: 'int'},
|
||||
{title: 'Double', value: 'double'},
|
||||
{title: 'Boolean', value: 'boolean'}
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '日期时间',
|
||||
key: 'datetime',
|
||||
type: JVXETypes.datetime,
|
||||
width: '200px',
|
||||
defaultValue: '2019-4-30 14:52:22',
|
||||
placeholder: '请选择',
|
||||
},
|
||||
{
|
||||
title: '复选框',
|
||||
key: 'checkbox',
|
||||
type: JVXETypes.checkbox,
|
||||
width: '100px',
|
||||
customValue: ['Y', 'N'], // true ,false
|
||||
defaultChecked: false,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
type: JVXETypes.slot,
|
||||
fixed: 'right',
|
||||
minWidth: '100px',
|
||||
align: 'center',
|
||||
slotName: 'action',
|
||||
}
|
||||
],
|
||||
dataSource: [],
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
created() {
|
||||
this.randomPage(0, 20, true)
|
||||
},
|
||||
methods: {
|
||||
|
||||
handleCK(props) {
|
||||
this.$message.success('请在控制台查看输出')
|
||||
// 参数介绍:
|
||||
// props.value 当前单元格的值
|
||||
// props.row 当前行的数据
|
||||
// props.rowId 当前行ID
|
||||
// props.rowIndex 当前行下标
|
||||
// props.column 当前列的配置
|
||||
// props.columnIndex 当前列下标
|
||||
// props.$table vxe实例,可以调用vxe内置方法
|
||||
// props.target JVXE实例,可以调用JVXE内置方法
|
||||
// props.caseId JVXE实例唯一ID
|
||||
// props.scrolling 是否正在滚动
|
||||
// props.triggerChange 触发change事件,用于更改slot的值
|
||||
console.log('查看: ', {props})
|
||||
},
|
||||
|
||||
handleDL(props) {
|
||||
// 调用删除方法
|
||||
props.target.removeRows(props.row)
|
||||
},
|
||||
|
||||
handleValueChange(event) {
|
||||
console.log('handleValueChange.event: ', event)
|
||||
},
|
||||
|
||||
/** 表单验证 */
|
||||
handleTableCheck() {
|
||||
this.$refs.vTable.validateTable().then(errMap => {
|
||||
if (errMap) {
|
||||
console.log('表单验证未通过:', {errMap})
|
||||
this.$message.error('验证未通过,请在控制台查看详细')
|
||||
} else {
|
||||
this.$message.success('验证通过')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/** 获取值,忽略表单验证 */
|
||||
handleTableGet() {
|
||||
const values = this.$refs.vTable.getTableData()
|
||||
console.log('获取值:', {values})
|
||||
this.$message.success('获取值成功,请看控制台输出')
|
||||
},
|
||||
|
||||
/** 模拟加载1000条数据 */
|
||||
handleTableSet() {
|
||||
this.randomPage(1, 1000, true)
|
||||
},
|
||||
|
||||
/* 随机生成数据 */
|
||||
randomPage(current, pageSize, loading = false) {
|
||||
if (loading) {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
let randomDatetime = () => {
|
||||
let time = randomNumber(1000, 9999999999999)
|
||||
return moment(new Date(time)).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
let limit = (current - 1) * pageSize
|
||||
|
||||
let options = ['string', 'int', 'double', 'boolean']
|
||||
|
||||
let begin = Date.now()
|
||||
let values = []
|
||||
for (let i = 0; i < pageSize; i++) {
|
||||
values.push({
|
||||
id: randomUUID(),
|
||||
normal: `normal-${(limit + i) + 1}`,
|
||||
input: `text-${(limit + i) + 1}`,
|
||||
textarea: `textarea-${(limit + i) + 1}`,
|
||||
number: randomNumber(0, 233),
|
||||
select: options[randomNumber(0, 3)],
|
||||
select_dict: randomNumber(1, 2).toString(),
|
||||
select_multiple: (() => {
|
||||
let length = randomNumber(1, 4)
|
||||
let arr = []
|
||||
for (let j = 0; j < length; j++) {
|
||||
pushIfNotExist(arr, options[randomNumber(0, 3)])
|
||||
}
|
||||
return arr
|
||||
})(),
|
||||
select_search: options[randomNumber(0, 3)],
|
||||
datetime: randomDatetime(),
|
||||
checkbox: ['Y', 'N'][randomNumber(0, 1)]
|
||||
})
|
||||
}
|
||||
|
||||
this.dataSource = values
|
||||
let end = Date.now()
|
||||
let diff = end - begin
|
||||
|
||||
if (loading && diff < pageSize) {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, pageSize - diff)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<j-vxe-table
|
||||
ref="vTable"
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
keep-source
|
||||
:height="484"
|
||||
:loading="loading"
|
||||
:dataSource="dataSource"
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
style="margin-top: 8px;"
|
||||
@pageChange="handlePageChange"
|
||||
>
|
||||
|
||||
<template v-slot:toolbarSuffix>
|
||||
<a-button @click="handleTableGet">获取值</a-button>
|
||||
</template>
|
||||
|
||||
</j-vxe-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'moment'
|
||||
import { randomNumber, randomUUID } from '@/utils/util'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
|
||||
export default {
|
||||
name: 'JVxeDemo2',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
columns: [
|
||||
{
|
||||
title: '下拉框_字典表搜索',
|
||||
key: 'select_dict_search',
|
||||
type: JVXETypes.selectDictSearch,
|
||||
width: '200px',
|
||||
// 【字典表配置信息】:数据库表名,显示字段名,存储字段名
|
||||
dict: 'sys_user,realname,username',
|
||||
},
|
||||
{
|
||||
title: 'JPopup',
|
||||
key: 'popup',
|
||||
type: JVXETypes.popup,
|
||||
width: '180px',
|
||||
popupCode: 'demo',
|
||||
field: 'name,sex,age',
|
||||
orgFields: 'name,sex,age',
|
||||
destFields: 'popup,popup_sex,popup_age'
|
||||
},
|
||||
{
|
||||
title: 'JP-性别',
|
||||
key: 'popup_sex',
|
||||
type: JVXETypes.select,
|
||||
dictCode: 'sex',
|
||||
disabled: true,
|
||||
width: '100px',
|
||||
},
|
||||
{
|
||||
title: 'JP-年龄',
|
||||
key: 'popup_age',
|
||||
type: JVXETypes.normal,
|
||||
width: '80px',
|
||||
},
|
||||
{
|
||||
title: '进度条',
|
||||
key: 'progress',
|
||||
type: JVXETypes.progress,
|
||||
minWidth: '120px'
|
||||
},
|
||||
{
|
||||
title: '单选',
|
||||
key: 'radio',
|
||||
type: JVXETypes.radio,
|
||||
width: '130px',
|
||||
options: [
|
||||
{text: '男', value: '1'},
|
||||
{text: '女', value: '2'},
|
||||
],
|
||||
// 允许清除选择(再点一次取消选择)
|
||||
allowClear: true
|
||||
},
|
||||
{
|
||||
title: '上传',
|
||||
key: 'upload',
|
||||
type: JVXETypes.upload,
|
||||
width: '180px',
|
||||
btnText: '点击上传',
|
||||
token: true,
|
||||
responseName: 'message',
|
||||
action: window._CONFIG['domianURL'] + '/sys/common/upload'
|
||||
},
|
||||
{
|
||||
title: '图片上传',
|
||||
key: 'image',
|
||||
type: JVXETypes.image,
|
||||
width: '180px',
|
||||
token: true,
|
||||
},
|
||||
{
|
||||
title: '文件上传',
|
||||
key: 'file',
|
||||
type: JVXETypes.file,
|
||||
width: '180px',
|
||||
token: true,
|
||||
},
|
||||
],
|
||||
dataSource: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: ['10', '20', '30', '100', '200'],
|
||||
total: 1000,
|
||||
},
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
created() {
|
||||
this.randomPage(this.pagination.current, this.pagination.pageSize, true)
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 当分页参数变化时触发的事件
|
||||
handlePageChange(event) {
|
||||
// 重新赋值
|
||||
this.pagination.current = event.current
|
||||
this.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.randomPage(event.current, event.pageSize, true)
|
||||
},
|
||||
|
||||
/** 获取值,忽略表单验证 */
|
||||
handleTableGet() {
|
||||
const values = this.$refs.vTable.getTableData()
|
||||
console.log('获取值:', {values})
|
||||
this.$message.success('获取值成功,请看控制台输出')
|
||||
},
|
||||
|
||||
/* 随机生成数据 */
|
||||
randomPage(current, pageSize, loading = false) {
|
||||
if (loading) {
|
||||
this.loading = true
|
||||
}
|
||||
|
||||
let randomDatetime = () => {
|
||||
let time = randomNumber(1000, 9999999999999)
|
||||
return moment(new Date(time)).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
let limit = (current - 1) * pageSize
|
||||
|
||||
let begin = Date.now()
|
||||
let values = []
|
||||
for (let i = 0; i < pageSize; i++) {
|
||||
let radio = randomNumber(0, 2)
|
||||
values.push({
|
||||
id: randomUUID(),
|
||||
select_dict_search: ['', 'admin', '', 'jeecg', ''][randomNumber(0, 4)],
|
||||
progress: randomNumber(0, 100),
|
||||
radio: radio ? radio.toString() : null
|
||||
})
|
||||
}
|
||||
|
||||
this.dataSource = values
|
||||
let end = Date.now()
|
||||
let diff = end - begin
|
||||
|
||||
if (loading && diff < pageSize) {
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
}, pageSize - diff)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,234 @@
|
|||
<template>
|
||||
<a-card title="即时保存示例" :bordered="false">
|
||||
<!--
|
||||
【即时保存大体思路】:
|
||||
1. JVxeTable 上必须加 keep-source 属性
|
||||
2. 监听 edit-closed事件,这个事件是在编辑完成后触发
|
||||
3. 在这个事件里面判断数据是否更改,如果更改了就调用接口进行保存操作
|
||||
-->
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
:toolbarConfig="toolbarConfig"
|
||||
|
||||
row-number
|
||||
row-selection
|
||||
keep-source
|
||||
async-remove
|
||||
|
||||
:height="340"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:pagination="pagination"
|
||||
|
||||
@save="handleTableSave"
|
||||
@remove="handleTableRemove"
|
||||
@edit-closed="handleEditClosed"
|
||||
@pageChange="handlePageChange"
|
||||
@selectRowChange="handleSelectRowChange"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction, postAction, putAction } from '@api/manage'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
|
||||
// 即时保存示例
|
||||
export default {
|
||||
name: 'JSBCDemo',
|
||||
data() {
|
||||
return {
|
||||
// 工具栏的按钮配置
|
||||
toolbarConfig: {
|
||||
// add 新增按钮;remove 删除按钮;clearSelection 清空选择按钮
|
||||
btn: ['add', 'save', 'remove', 'clearSelection']
|
||||
},
|
||||
// 是否正在加载
|
||||
loading: false,
|
||||
// 分页器参数
|
||||
pagination: {
|
||||
// 当前页码
|
||||
current: 1,
|
||||
// 每页的条数
|
||||
pageSize: 200,
|
||||
// 可切换的条数
|
||||
pageSizeOptions: ['10', '20', '30', '100', '200'],
|
||||
// 数据总数(目前并不知道真实的总数,所以先填写0,在后台查出来后再赋值)
|
||||
total: 0,
|
||||
},
|
||||
// 选择的行
|
||||
selectedRows: [],
|
||||
// 数据源,控制表格的数据
|
||||
dataSource: [],
|
||||
// 列配置,控制表格显示的列
|
||||
columns: [
|
||||
{key: 'num', title: '序号', width: '80px'},
|
||||
{
|
||||
// 字段key,跟后台数据的字段名匹配
|
||||
key: 'ship_name',
|
||||
// 列的标题
|
||||
title: '船名',
|
||||
// 列的宽度
|
||||
width: '180px',
|
||||
// 如果加上了该属性,就代表当前单元格是可编辑的,type就是表单的类型,input就是简单的输入框
|
||||
type: JVXETypes.input
|
||||
},
|
||||
{key: 'call', title: '呼叫', width: '80px', type: JVXETypes.input},
|
||||
{key: 'len', title: '长', width: '80px', type: JVXETypes.input},
|
||||
{key: 'ton', title: '吨', width: '120px', defaultValue: 233, type: JVXETypes.input},
|
||||
{key: 'payer', title: '付款方', width: '120px', defaultValue: '张三', type: JVXETypes.input},
|
||||
{key: 'count', title: '数', width: '40px'},
|
||||
{
|
||||
key: 'company',
|
||||
title: '公司',
|
||||
// 最小宽度,与宽度不同的是,这个不是固定的宽度,如果表格有多余的空间,会平均分配给设置了 minWidth 的列
|
||||
// 如果要做占满表格的列可以这么写
|
||||
minWidth: '180px',
|
||||
type: JVXETypes.input
|
||||
},
|
||||
{key: 'trend', title: '动向', width: '120px', type: JVXETypes.input},
|
||||
],
|
||||
// 查询url地址
|
||||
url: {
|
||||
getData: '/mock/vxe/getData',
|
||||
// 模拟保存单行数据(即时保存)
|
||||
saveRow: '/mock/vxe/immediateSaveRow',
|
||||
// 模拟保存整个表格的数据
|
||||
saveAll: '/mock/vxe/immediateSaveAll',
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 加载数据
|
||||
loadData() {
|
||||
// 封装查询条件
|
||||
let formData = {
|
||||
pageNo: this.pagination.current,
|
||||
pageSize: this.pagination.pageSize
|
||||
}
|
||||
// 调用查询数据接口
|
||||
this.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
// 后台查询回来的 total,数据总数量
|
||||
this.pagination.total = res.result.total
|
||||
// 将查询的数据赋值给 dataSource
|
||||
this.dataSource = res.result.records
|
||||
// 重置选择
|
||||
this.selectedRows = []
|
||||
} else {
|
||||
this.$error({title: '主表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
// 这里是无论成功或失败都会执行的方法,在这里关闭loading
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 【整体保存】点击保存按钮时触发的事件
|
||||
handleTableSave({$table, target}) {
|
||||
// 校验整个表格
|
||||
$table.validate().then((errMap) => {
|
||||
// 校验通过
|
||||
if (!errMap) {
|
||||
// 获取所有数据
|
||||
let tableData = target.getTableData()
|
||||
console.log('当前保存的数据是:', tableData)
|
||||
// 获取新增的数据
|
||||
let newData = target.getNewData()
|
||||
console.log('-- 新增的数据:', newData)
|
||||
// 获取删除的数据
|
||||
let deleteData = target.getDeleteData()
|
||||
console.log('-- 删除的数据:', deleteData)
|
||||
|
||||
// 【模拟保存】
|
||||
this.loading = true
|
||||
postAction(this.url.saveAll, tableData).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success(`保存成功!`)
|
||||
} else {
|
||||
this.$message.warn(`保存失败:` + res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 触发单元格删除事件
|
||||
handleTableRemove(event) {
|
||||
|
||||
// 把 event.deleteRows 传给后台进行删除(注意:这里不会传递前端逻辑新增的数据,因为不需要请求后台删除)
|
||||
console.log('待删除的数据: ', event.deleteRows)
|
||||
// 也可以只传ID,因为可以根据ID删除
|
||||
let deleteIds = event.deleteRows.map(row => row.id)
|
||||
console.log('待删除的数据ids: ', deleteIds)
|
||||
|
||||
// 模拟请求后台删除
|
||||
this.loading = true
|
||||
window.setTimeout(() => {
|
||||
this.loading = false
|
||||
this.$message.success('删除成功')
|
||||
// 假设后台返回删除成功,必须要调用 confirmRemove() 方法,才会真正在表格里移除(会同时删除选中的逻辑新增的数据)
|
||||
event.confirmRemove()
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 单元格编辑完成之后触发的事件
|
||||
handleEditClosed(event) {
|
||||
let {$table, row, column} = event
|
||||
|
||||
let field = column.property
|
||||
let cellValue = row[field]
|
||||
// 判断单元格值是否被修改
|
||||
if ($table.isUpdateByRow(row, field)) {
|
||||
// 校验当前行
|
||||
$table.validate(row).then((errMap) => {
|
||||
// 校验通过
|
||||
if (!errMap) {
|
||||
// 【模拟保存】
|
||||
let hideLoading = this.$message.loading(`正在保存"${column.title}"`, 0)
|
||||
console.log('即时保存数据:', row)
|
||||
putAction(this.url.saveRow, row).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success(`"${column.title}"保存成功!`)
|
||||
// 局部更新单元格为已保存状态
|
||||
$table.reloadRow(row, null, field)
|
||||
} else {
|
||||
this.$message.warn(`"${column.title}"保存失败:` + res.message)
|
||||
}
|
||||
}).finally(() => {
|
||||
hideLoading()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 当分页参数变化时触发的事件
|
||||
handlePageChange(event) {
|
||||
// 重新赋值
|
||||
this.pagination.current = event.current
|
||||
this.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.loadData()
|
||||
},
|
||||
|
||||
// 当选择的行变化时触发的事件
|
||||
handleSelectRowChange(event) {
|
||||
this.selectedRows = event.selectedRows
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,278 @@
|
|||
<template>
|
||||
<a-card title="弹出子表示例" :bordered="false">
|
||||
|
||||
<!--
|
||||
【弹出子表大体思路】
|
||||
1. 必须要有 click-row-show-sub-form 属性,如果该属性设为false,那么就不会弹出子表
|
||||
2. 必须要有 sub-form 插槽,用于规定弹出子表的内容
|
||||
3. highlight-current-row 属性可有可无,如果有则点击一行的时候,该行会背景色会常亮
|
||||
-->
|
||||
|
||||
<!--
|
||||
【弹出详细信息(既有主表的数据也有子表的)大体思路】
|
||||
1. 必须要有 click-row-show-main-form 属性,如果该属性设为false,那么就不会弹出详细信息
|
||||
2. 必须要有 main-form 插槽,用于规定弹出子表的内容
|
||||
3. 可选 click-row-show-sub-form 属性,如果有该属性,就会显示子表,否者不显示
|
||||
-->
|
||||
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
|
||||
highlight-current-row
|
||||
click-row-show-sub-form
|
||||
click-row-show-main-form
|
||||
|
||||
:height="750"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
@detailsConfirm="handleDetailsConfirm"
|
||||
>
|
||||
|
||||
<!-- 主表单 -->
|
||||
<template v-slot:mainForm="{row}">
|
||||
<template v-if="row">
|
||||
<a-form-model
|
||||
ref="form2"
|
||||
:model="row"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="ID" prop="id">
|
||||
<a-input v-model="row.id" disabled/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="序号" prop="num">
|
||||
<a-input v-model="row.num"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="船名" prop="ship_name">
|
||||
<a-input v-model="row.ship_name"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="呼叫" prop="call">
|
||||
<a-input v-model="row.call"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="长" prop="len">
|
||||
<a-input v-model="row.len"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="吨" prop="ton">
|
||||
<a-input v-model="row.ton"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="付款方" prop="payer">
|
||||
<a-input v-model="row.payer"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="数" prop="count">
|
||||
<a-input v-model="row.count"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="公司" prop="company">
|
||||
<a-input v-model="row.company"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-model-item label="动向" prop="trend">
|
||||
<a-input v-model="row.trend"/>
|
||||
</a-form-model-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-model>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- 子表单 -->
|
||||
<template v-slot:subForm="{row}">
|
||||
<template v-if="loadSubData(row)">
|
||||
<j-vxe-table
|
||||
ref="subFormTable"
|
||||
height="auto"
|
||||
:max-height="350"
|
||||
:loading="subTable.loading"
|
||||
:columns="subTable.columns"
|
||||
:dataSource="subTable.dataSource"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</j-vxe-table>
|
||||
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@api/manage'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
|
||||
// 弹出子表示例
|
||||
export default {
|
||||
name: 'PopupSubTable',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
dataSource: [],
|
||||
columns: [
|
||||
{key: 'num', title: '序号', width: '80px'},
|
||||
{key: 'ship_name', title: '船名', width: '180px', type: JVXETypes.input},
|
||||
{key: 'call', title: '呼叫', width: '80px'},
|
||||
{key: 'len', title: '长', width: '80px'},
|
||||
{key: 'ton', title: '吨', width: '120px'},
|
||||
{key: 'payer', title: '付款方', width: '120px'},
|
||||
{key: 'count', title: '数', width: '40px'},
|
||||
{
|
||||
key: 'company',
|
||||
title: '公司',
|
||||
minWidth: '180px',
|
||||
// 是否点击显示详细信息
|
||||
// 只有当前单元格不能编辑的时候才能生效
|
||||
// 如果不设的话,点击就只弹出子表,不会弹出主表的详细信息
|
||||
showDetails: true
|
||||
},
|
||||
{key: 'trend', title: '动向', width: '120px'},
|
||||
],
|
||||
// 子表的信息
|
||||
subTable: {
|
||||
currentRowId: null,
|
||||
loading: false,
|
||||
pagination: {current: 1, pageSize: 200, pageSizeOptions: ['100', '200'], total: 0},
|
||||
selectedRows: [],
|
||||
dataSource: [],
|
||||
columns: [
|
||||
{key: 'dd_num', title: '调度序号', width: '120px'},
|
||||
{key: 'tug', title: '拖轮', width: '180px', type: JVXETypes.input},
|
||||
{key: 'work_start_time', title: '作业开始时间', width: '180px', type: JVXETypes.input},
|
||||
{key: 'work_stop_time', title: '作业结束时间', width: '180px', type: JVXETypes.input},
|
||||
{key: 'type', title: '船舶分类', width: '120px', type: JVXETypes.input},
|
||||
{key: 'port_area', title: '所属港区', minWidth: '120px', type: JVXETypes.input},
|
||||
],
|
||||
},
|
||||
// 查询url地址
|
||||
url: {
|
||||
getData: '/mock/vxe/getData',
|
||||
},
|
||||
// 主表form表单字段
|
||||
mainForm: {
|
||||
id: '',
|
||||
num: '',
|
||||
ship_name: '',
|
||||
call: '',
|
||||
len: '',
|
||||
ton: '',
|
||||
payer: '',
|
||||
count: '',
|
||||
company: '',
|
||||
trend: '',
|
||||
},
|
||||
// form表单 col
|
||||
labelCol: {span: 4},
|
||||
wrapperCol: {span: 20},
|
||||
rules: {
|
||||
num: [
|
||||
{required: true, message: '必须输入序号'},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
|
||||
log: console.log,
|
||||
|
||||
// 加载数据
|
||||
loadData() {
|
||||
// 封装查询条件
|
||||
let formData = {pageNo: 1, pageSize: 30}
|
||||
// 调用查询数据接口
|
||||
this.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
// 将查询的数据赋值给 dataSource
|
||||
this.dataSource = res.result.records
|
||||
// 重置选择
|
||||
this.selectedRows = []
|
||||
} else {
|
||||
this.$error({title: '主表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
// 这里是无论成功或失败都会执行的方法,在这里关闭loading
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 查询子表数据
|
||||
loadSubData(row) {
|
||||
if (row) {
|
||||
// 这里一定要做限制,限制不能重复查询,否者会出现死循环
|
||||
if (this.subTable.currentRowId === row.id) {
|
||||
return true
|
||||
}
|
||||
this.subTable.currentRowId = row.id
|
||||
let formData = {pageNo: 1, pageSize: 30, parentId: row.id}
|
||||
this.subTable.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
// 将查询的数据赋值给 dataSource
|
||||
this.subTable.dataSource = res.result.records
|
||||
} else {
|
||||
this.$error({title: '主表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
// 这里是无论成功或失败都会执行的方法,在这里关闭loading
|
||||
this.subTable.loading = false
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
// 详细信息里点了确认按钮
|
||||
handleDetailsConfirm({row, $table, callback}) {
|
||||
console.log('保存的数据:', row)
|
||||
// 校验当前行
|
||||
$table.validate(row).then((errMap) => {
|
||||
// 校验通过
|
||||
if (!errMap) {
|
||||
// 校验子表,如果需要的话,可以操作下面这个对象:
|
||||
// this.$refs.subFormTable
|
||||
|
||||
callback(true)
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.$message.success('保存成功')
|
||||
}, 1000)
|
||||
} else {
|
||||
callback(false)
|
||||
this.$message.warn('校验失败')
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,119 @@
|
|||
<template>
|
||||
<a-card title="无痕刷新示例" :bordered="false">
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
<span>启用数据变动特效:</span>
|
||||
<a-switch v-model="reloadEffect"/>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
【即时保存大体思路】:
|
||||
1. 该功能依赖于【即时保存】功能,请先看即时保存示例
|
||||
2. 必须要有 socket-reload 属性,且设为 true
|
||||
3. 必须要有 socket-key 属性,该属性为当前表格的唯一标识,
|
||||
系统会自动更新所有 socket-key 相同的表格
|
||||
4. 在局部保存 edit-closed 事件中,
|
||||
保存成功后调用 socketSendUpdateRow 方法,将当前 row 传递过去即可 (见第 108 行)
|
||||
-->
|
||||
<j-vxe-table
|
||||
ref="table"
|
||||
row-number
|
||||
row-selection
|
||||
|
||||
keep-source
|
||||
socket-reload
|
||||
socket-key="demo-socket-reload"
|
||||
:reload-effect="reloadEffect"
|
||||
|
||||
:height="340"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
@edit-closed="handleEditClosed"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@api/manage'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
|
||||
// 无痕刷新示例
|
||||
export default {
|
||||
name: 'SocketReload',
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
dataSource: [],
|
||||
columns: [
|
||||
{key: 'num', title: '序号', width: '80px'},
|
||||
{key: 'ship_name', title: '船名', width: '180px', type: JVXETypes.input},
|
||||
{key: 'call', title: '呼叫', width: '80px', type: JVXETypes.input},
|
||||
{key: 'len', title: '长', width: '80px', type: JVXETypes.input},
|
||||
{key: 'ton', title: '吨', width: '120px', type: JVXETypes.input},
|
||||
{key: 'payer', title: '付款方', width: '120px', type: JVXETypes.input},
|
||||
{key: 'count', title: '数', width: '40px'},
|
||||
{key: 'company', title: '公司', minWidth: '180px', type: JVXETypes.input},
|
||||
{key: 'trend', title: '动向', width: '120px', type: JVXETypes.input},
|
||||
],
|
||||
// 查询url地址
|
||||
url: {
|
||||
getData: '/mock/vxe/getData',
|
||||
},
|
||||
// 是否启用日历刷新效果
|
||||
reloadEffect: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
// 加载数据
|
||||
loadData() {
|
||||
let formData = {pageNo: 1, pageSize: 200}
|
||||
this.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
this.dataSource = res.result.records
|
||||
} else {
|
||||
this.$error({title: '主表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 单元格编辑完成之后触发的事件
|
||||
handleEditClosed(event) {
|
||||
let {$table, row, column} = event
|
||||
|
||||
let field = column.property
|
||||
let cellValue = row[field]
|
||||
// 判断单元格值是否被修改
|
||||
if ($table.isUpdateByRow(row, field)) {
|
||||
// 校验当前行
|
||||
$table.validate(row).then((errMap) => {
|
||||
// 校验通过
|
||||
if (!errMap) {
|
||||
// 【模拟保存】(此处需要替换成真实的请求)
|
||||
let hideLoading = this.$message.loading(`正在保存"${column.title}"`, 0)
|
||||
setTimeout(() => {
|
||||
hideLoading()
|
||||
this.$message.success(`"${column.title}"保存成功!`)
|
||||
// 局部更新单元格为已保存状态
|
||||
$table.reloadRow(row, null, field)
|
||||
// 发送更新消息
|
||||
this.$refs.table.socketSendUpdateRow(row)
|
||||
}, 555)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,308 @@
|
|||
<template>
|
||||
|
||||
<a-card :bordered="false">
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
:toolbarConfig="toolbarConfig"
|
||||
|
||||
row-number
|
||||
row-selection
|
||||
row-selection-type="radio"
|
||||
highlight-current-row
|
||||
click-select-row
|
||||
:height="tableHeight"
|
||||
:loading="table1.loading"
|
||||
:columns="table1.columns"
|
||||
:dataSource="table1.dataSource"
|
||||
:pagination="table1.pagination"
|
||||
:expand-config="expandConfig"
|
||||
style="margin-bottom: 8px"
|
||||
|
||||
@pageChange="handleTable1PageChange"
|
||||
@selectRowChange="handleTable1SelectRowChange"
|
||||
></j-vxe-table>
|
||||
|
||||
<a-tabs v-show="subTabs.show" :class="{'sub-tabs':true, 'un-expand': !subTabs.expand}">
|
||||
<a-tab-pane tab="子表1" key="1">
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
height="auto"
|
||||
:maxHeight="350"
|
||||
:loading="table2.loading"
|
||||
:columns="table2.columns"
|
||||
:dataSource="table2.dataSource"
|
||||
:pagination="table2.pagination"
|
||||
@pageChange="handleTable2PageChange"
|
||||
@selectRowChange="handleTable2SelectRowChange"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="子表2" key="2">
|
||||
<h1>这里是子表2</h1>
|
||||
<h1>这里是子表2</h1>
|
||||
<h1>这里是子表2</h1>
|
||||
<h1>这里是子表2</h1>
|
||||
<h1>这里是子表2</h1>
|
||||
<h1>这里是子表2</h1>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
import { getAction } from '@api/manage'
|
||||
|
||||
export default {
|
||||
name: 'ErpTemplate',
|
||||
data() {
|
||||
return {
|
||||
toolbarConfig: {
|
||||
// prefix 前缀;suffix 后缀
|
||||
slot: ['prefix', 'suffix'],
|
||||
// add 新增按钮;remove 删除按钮;clearSelection 清空选择按钮
|
||||
btn: ['add', 'remove', 'clearSelection']
|
||||
},
|
||||
|
||||
expandConfig: {
|
||||
// 是否只能同时展开一行
|
||||
accordion: true
|
||||
},
|
||||
|
||||
// 子表 tabs
|
||||
subTabs: {
|
||||
show: false,
|
||||
// 是否展开
|
||||
expand: true,
|
||||
// 是否自动展开
|
||||
autoExpand: true,
|
||||
},
|
||||
|
||||
table1: {
|
||||
// 是否正在加载
|
||||
loading: false,
|
||||
// 分页器参数
|
||||
pagination: {
|
||||
// 当前页码
|
||||
current: 1,
|
||||
// 每页的条数
|
||||
pageSize: 200,
|
||||
// 可切换的条数
|
||||
pageSizeOptions: ['10', '20', '30', '100', '200'],
|
||||
// 数据总数(目前并不知道真实的总数,所以先填写0,在后台查出来后再赋值)
|
||||
total: 0,
|
||||
showTotal: (total, range) => {
|
||||
// 此处为 jsx 语法
|
||||
let text = <span>{range[0] + '-' + range[1] + ' 共 ' + total + ' 条'}</span>
|
||||
// 判断子表是否显示,如果显示就渲染展开收起按钮
|
||||
if (this.subTabs.show) {
|
||||
let expand = (<span>
|
||||
<a-button type="link" onClick={this.handleToggleTabs}>
|
||||
<a-icon type={this.subTabs.expand ? 'up' : 'down'}/>
|
||||
<span>{this.subTabs.expand ? '收起' : '展开'}</span>
|
||||
</a-button>
|
||||
<a-checkbox vModel={this.subTabs.autoExpand}>自动展开</a-checkbox>
|
||||
</span>)
|
||||
// 返回多个dom用数组
|
||||
return [expand, text]
|
||||
} else {
|
||||
// 直接返回单个dom
|
||||
return text
|
||||
}
|
||||
},
|
||||
},
|
||||
// 选择的行
|
||||
selectedRows: [],
|
||||
// 数据源,控制表格的数据
|
||||
dataSource: [],
|
||||
// 列配置,控制表格显示的列
|
||||
columns: [
|
||||
{key: 'num', title: '序号', width: '80px'},
|
||||
{
|
||||
// 字段key,跟后台数据的字段名匹配
|
||||
key: 'ship_name',
|
||||
// 列的标题
|
||||
title: '船名',
|
||||
// 列的宽度
|
||||
width: '180px',
|
||||
// 如果加上了该属性,就代表当前单元格是可编辑的,type就是表单的类型,input就是简单的输入框
|
||||
type: JVXETypes.input
|
||||
},
|
||||
{key: 'call', title: '呼叫', width: '990px', type: JVXETypes.input},
|
||||
{key: 'len', title: '长', width: '80px', type: JVXETypes.inputNumber},
|
||||
{key: 'ton', title: '吨', width: '120px', type: JVXETypes.inputNumber},
|
||||
{key: 'payer', title: '付款方', width: '120px', type: JVXETypes.input},
|
||||
{key: 'count', title: '数', width: '40px'},
|
||||
{
|
||||
key: 'company',
|
||||
title: '公司',
|
||||
// 最小宽度,与宽度不同的是,这个不是固定的宽度,如果表格有多余的空间,会平均分配给设置了 minWidth 的列
|
||||
// 如果要做占满表格的列可以这么写
|
||||
minWidth: '180px',
|
||||
type: JVXETypes.input
|
||||
},
|
||||
{key: 'trend', title: '动向', width: '120px', type: JVXETypes.input},
|
||||
],
|
||||
},
|
||||
// 子级表的配置信息 (配置和主表的完全一致,就不写冗余的注释了)
|
||||
table2: {
|
||||
currentRowId: null,
|
||||
loading: false,
|
||||
pagination: {current: 1, pageSize: 10, pageSizeOptions: ['5', '10', '20', '30'], total: 0},
|
||||
selectedRows: [],
|
||||
dataSource: [],
|
||||
columns: [
|
||||
{key: 'dd_num', title: '调度序号', width: '120px'},
|
||||
{key: 'tug', title: '拖轮', width: '180px', type: JVXETypes.input},
|
||||
{key: 'work_start_time', title: '作业开始时间', width: '180px', type: JVXETypes.input},
|
||||
{key: 'work_stop_time', title: '作业结束时间', width: '180px', type: JVXETypes.input},
|
||||
{key: 'type', title: '船舶分类', width: '120px', type: JVXETypes.input},
|
||||
{key: 'port_area', title: '所属港区', width: '120px', type: JVXETypes.input},
|
||||
],
|
||||
},
|
||||
currentSubRow: null,
|
||||
// 查询url地址
|
||||
url: {
|
||||
getData: '/mock/vxe/getData',
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tableHeight() {
|
||||
let {show, expand} = this.subTabs
|
||||
return show ? (expand ? 350 : 482) : 482
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadTable1Data()
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 加载table1【主表】的数据
|
||||
loadTable1Data() {
|
||||
// 封装查询条件
|
||||
let formData = {
|
||||
pageNo: this.table1.pagination.current,
|
||||
pageSize: this.table1.pagination.pageSize
|
||||
}
|
||||
// 调用查询数据接口
|
||||
this.table1.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
// 后台查询回来的 total,数据总数量
|
||||
this.table1.pagination.total = res.result.total
|
||||
// 将查询的数据赋值给 dataSource
|
||||
this.table1.dataSource = res.result.records
|
||||
// 重置选择
|
||||
this.table1.selectedRows = []
|
||||
} else {
|
||||
this.$error({title: '主表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
// 这里是无论成功或失败都会执行的方法,在这里关闭loading
|
||||
this.table1.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 查询子表数据
|
||||
loadSubData(row) {
|
||||
if (row) {
|
||||
// 这里一定要做限制,限制不能重复查询,否者会出现死循环
|
||||
if (this.table2.currentRowId === row.id) {
|
||||
return true
|
||||
}
|
||||
this.table2.currentRowId = row.id
|
||||
this.loadTable2Data()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
// 查询子表数据
|
||||
loadTable2Data() {
|
||||
let table2 = this.table2
|
||||
let formData = {
|
||||
parentId: table2.currentRowId,
|
||||
pageNo: this.table2.pagination.current,
|
||||
pageSize: this.table2.pagination.pageSize
|
||||
}
|
||||
table2.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
// 将查询的数据赋值给 dataSource
|
||||
table2.selectedRows = []
|
||||
table2.dataSource = res.result.records
|
||||
table2.pagination.total = res.result.total
|
||||
} else {
|
||||
this.$error({title: '子表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
// 这里是无论成功或失败都会执行的方法,在这里关闭loading
|
||||
table2.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
// table1【主表】当选择的行变化时触发的事件
|
||||
handleTable1SelectRowChange(event) {
|
||||
this.table1.selectedRows = event.selectedRows
|
||||
this.subTabs.show = true
|
||||
if (this.subTabs.autoExpand) {
|
||||
this.subTabs.expand = true
|
||||
}
|
||||
this.loadSubData(event.selectedRows[0])
|
||||
},
|
||||
// table2【子表】当选择的行变化时触发的事件
|
||||
handleTable2SelectRowChange(event) {
|
||||
this.table2.selectedRows = event.selectedRows
|
||||
},
|
||||
|
||||
handleTable1PageChange(event) {
|
||||
// 重新赋值
|
||||
this.table1.pagination.current = event.current
|
||||
this.table1.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.loadTable1Data()
|
||||
},
|
||||
// 当table2【子表】分页参数变化时触发的事件
|
||||
handleTable2PageChange(event) {
|
||||
// 重新赋值
|
||||
this.table2.pagination.current = event.current
|
||||
this.table2.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.loadTable2Data()
|
||||
},
|
||||
|
||||
// 展开或收起子表tabs
|
||||
handleToggleTabs() {
|
||||
this.subTabs.expand = !this.subTabs.expand
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sub-tabs {
|
||||
&.un-expand {
|
||||
/deep/ .ant-tabs-content {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
/deep/ .ant-tabs-bar {
|
||||
border-color: transparent !important;
|
||||
}
|
||||
|
||||
/deep/ .ant-tabs-ink-bar {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/deep/ .ant-tabs-tab {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-tabs>
|
||||
<a-tab-pane tab="ERP布局模板" key="erp">
|
||||
<erp-template/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="布局模板1" key="1">
|
||||
<template1/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="布局模板2" key="2">
|
||||
<template2/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="布局模板3" key="3">
|
||||
<template3/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="布局模板4" key="4">
|
||||
<template4/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane tab="布局模板5" key="5">
|
||||
<template5/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Template1 from './Template1'
|
||||
import Template2 from './Template2'
|
||||
import Template3 from './Template3'
|
||||
import Template4 from './Template4'
|
||||
import Template5 from './Template5'
|
||||
import ErpTemplate from './ErpTemplate'
|
||||
|
||||
export default {
|
||||
name: 'LayoutDemo',
|
||||
components: {Template5, Template4, Template3, Template2, Template1, ErpTemplate}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,330 @@
|
|||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-row :gutter="8">
|
||||
<!-- 这里是父级节点 -->
|
||||
<a-col :span="24" style="margin-bottom: 4px;">
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
click-select-row
|
||||
highlight-current-row
|
||||
:radio-config="{highlight: false}"
|
||||
:checkbox-config="{highlight: false}"
|
||||
:height="340"
|
||||
:loading="table1.loading"
|
||||
:columns="table1.columns"
|
||||
:dataSource="table1.dataSource"
|
||||
:pagination="table1.pagination"
|
||||
@pageChange="handleTable1PageChange"
|
||||
@selectRowChange="handleTable1SelectRowChange"
|
||||
/>
|
||||
</a-col>
|
||||
<!-- 这里是子级节点 -->
|
||||
<a-col :span="12">
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
click-select-row
|
||||
highlight-current-row
|
||||
:radio-config="{highlight: false}"
|
||||
:checkbox-config="{highlight: false}"
|
||||
:height="340"
|
||||
:loading="table2.loading"
|
||||
:columns="table2.columns"
|
||||
:dataSource="table2.dataSource"
|
||||
:pagination="table2.pagination"
|
||||
@pageChange="handleTable2PageChange"
|
||||
@selectRowChange="handleTable2SelectRowChange"
|
||||
>
|
||||
</j-vxe-table>
|
||||
</a-col>
|
||||
<!-- 这里是孙级节点 -->
|
||||
<a-col :span="12">
|
||||
<j-vxe-table
|
||||
toolbar
|
||||
row-number
|
||||
row-selection
|
||||
:height="340"
|
||||
:loading="table3.loading"
|
||||
:columns="table3.columns"
|
||||
:dataSource="table3.dataSource"
|
||||
:pagination="table3.pagination"
|
||||
@pageChange="handleTable3PageChange"
|
||||
>
|
||||
</j-vxe-table>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAction } from '@api/manage'
|
||||
import { JVXETypes } from '@/components/jeecg/JVxeTable'
|
||||
|
||||
// 【多种布局模板】上面父、左下子、右下孙
|
||||
export default {
|
||||
name: 'Template1',
|
||||
data() {
|
||||
return {
|
||||
table1: {
|
||||
// 是否正在加载
|
||||
loading: false,
|
||||
// 分页器参数
|
||||
pagination: {
|
||||
// 当前页码
|
||||
current: 1,
|
||||
// 每页的条数
|
||||
pageSize: 200,
|
||||
// 可切换的条数
|
||||
pageSizeOptions: ['10', '20', '30', '100', '200'],
|
||||
// 数据总数(目前并不知道真实的总数,所以先填写0,在后台查出来后再赋值)
|
||||
total: 0,
|
||||
},
|
||||
// 最后选中的行
|
||||
lastRow: null,
|
||||
// 选择的行
|
||||
selectedRows: [],
|
||||
// 数据源,控制表格的数据
|
||||
dataSource: [],
|
||||
// 列配置,控制表格显示的列
|
||||
columns: [
|
||||
{key: 'num', title: '序号', width: '80px'},
|
||||
{
|
||||
// 字段key,跟后台数据的字段名匹配
|
||||
key: 'ship_name',
|
||||
// 列的标题
|
||||
title: '船名',
|
||||
// 列的宽度
|
||||
width: '180px',
|
||||
// 如果加上了该属性,就代表当前单元格是可编辑的,type就是表单的类型,input就是简单的输入框
|
||||
type: JVXETypes.input,
|
||||
formatter({cellValue, row, column}) {
|
||||
let foo = ''
|
||||
if (row.company === '佧伒侾佯有限公司') {
|
||||
foo += '-233'
|
||||
}
|
||||
return cellValue + foo
|
||||
},
|
||||
},
|
||||
{key: 'call', title: '呼叫', width: '80px', type: JVXETypes.input},
|
||||
{key: 'len', title: '长', width: '80px', type: JVXETypes.inputNumber},
|
||||
{key: 'ton', title: '吨', width: '120px', type: JVXETypes.inputNumber},
|
||||
{key: 'payer', title: '付款方', width: '120px', type: JVXETypes.input},
|
||||
{key: 'count', title: '数', width: '40px'},
|
||||
{
|
||||
key: 'company',
|
||||
title: '公司',
|
||||
// 最小宽度,与宽度不同的是,这个不是固定的宽度,如果表格有多余的空间,会平均分配给设置了 minWidth 的列
|
||||
// 如果要做占满表格的列可以这么写
|
||||
minWidth: '180px',
|
||||
type: JVXETypes.input
|
||||
},
|
||||
{key: 'trend', title: '动向', width: '120px', type: JVXETypes.input},
|
||||
],
|
||||
},
|
||||
// 子级表的配置信息 (配置和主表的完全一致,就不写冗余的注释了)
|
||||
table2: {
|
||||
loading: false,
|
||||
pagination: {current: 1, pageSize: 200, pageSizeOptions: ['100', '200'], total: 0},
|
||||
// 最后选中的行
|
||||
lastRow: null,
|
||||
selectedRows: [],
|
||||
dataSource: [],
|
||||
columns: [
|
||||
{key: 'dd_num', title: '调度序号', width: '120px'},
|
||||
{key: 'tug', title: '拖轮', width: '180px', type: JVXETypes.input},
|
||||
{key: 'work_start_time', title: '作业开始时间', width: '180px', type: JVXETypes.input},
|
||||
{key: 'work_stop_time', title: '作业结束时间', width: '180px', type: JVXETypes.input},
|
||||
{key: 'type', title: '船舶分类', width: '120px', type: JVXETypes.input},
|
||||
{key: 'port_area', title: '所属港区', width: '120px', type: JVXETypes.input},
|
||||
],
|
||||
},
|
||||
// 孙级表的配置信息 (配置和主表的完全一致,就不写冗余的注释了)
|
||||
table3: {
|
||||
loading: false,
|
||||
pagination: {current: 1, pageSize: 200, pageSizeOptions: ['100', '200'], total: 0},
|
||||
selectedRows: [],
|
||||
dataSource: [],
|
||||
columns: [
|
||||
{key: 'dd_num', title: '调度序号', width: '120px'},
|
||||
{key: 'tug', title: '拖轮', width: '120px', type: JVXETypes.input},
|
||||
{key: 'power', title: '马力', width: '120px', type: JVXETypes.input},
|
||||
{key: 'nature', title: '性质', width: '120px', type: JVXETypes.input},
|
||||
{key: 'departure_time', title: '发船时间', width: '180px', type: JVXETypes.input},
|
||||
],
|
||||
},
|
||||
// 查询url地址
|
||||
url: {
|
||||
getData: '/mock/vxe/getData',
|
||||
},
|
||||
}
|
||||
},
|
||||
// 监听器
|
||||
watch: {
|
||||
// 监听table1 【主表】选择的数据发生了变化
|
||||
['table1.lastRow'](row) {
|
||||
this.loadTable2Data()
|
||||
},
|
||||
// 监听table2 【子表】选择的数据发生了变化
|
||||
['table2.lastRow']() {
|
||||
this.loadTable3Data()
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadTable1Data()
|
||||
},
|
||||
methods: {
|
||||
|
||||
// 加载table1【主表】的数据
|
||||
loadTable1Data() {
|
||||
// 封装查询条件
|
||||
let formData = {
|
||||
pageNo: this.table1.pagination.current,
|
||||
pageSize: this.table1.pagination.pageSize
|
||||
}
|
||||
// 调用查询数据接口
|
||||
this.table1.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
// 后台查询回来的 total,数据总数量
|
||||
this.table1.pagination.total = res.result.total
|
||||
// 将查询的数据赋值给 dataSource
|
||||
this.table1.dataSource = res.result.records
|
||||
// 重置选择
|
||||
this.table1.selectedRows = []
|
||||
} else {
|
||||
this.$error({title: '主表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
// 这里是无论成功或失败都会执行的方法,在这里关闭loading
|
||||
this.table1.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 当table1【主表】分页参数变化时触发的事件
|
||||
handleTable1PageChange(event) {
|
||||
// 重新赋值
|
||||
this.table1.pagination.current = event.current
|
||||
this.table1.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.loadTable1Data()
|
||||
},
|
||||
|
||||
// table1【主表】当选择的行变化时触发的事件
|
||||
handleTable1SelectRowChange(event) {
|
||||
this.handleTableSelectRowChange(this.table1, event)
|
||||
},
|
||||
|
||||
// 加载table2【子表】的数据,根据主表的id进行查询
|
||||
loadTable2Data() {
|
||||
// 如果主表没有选择,则不查询
|
||||
let selectedRows = this.table1.selectedRows
|
||||
if (!selectedRows || selectedRows.length === 0) {
|
||||
this.table2.pagination.total = 0
|
||||
this.table2.dataSource = []
|
||||
this.table2.selectedRows = []
|
||||
return
|
||||
} else if (this.table1.lastRow == null) {
|
||||
this.table1.lastRow = selectedRows[selectedRows.length - 1]
|
||||
}
|
||||
let formData = {
|
||||
parentId: this.table1.lastRow.id,
|
||||
pageNo: this.table2.pagination.current,
|
||||
pageSize: this.table2.pagination.pageSize
|
||||
}
|
||||
this.table2.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
this.table2.pagination.total = res.result.total
|
||||
this.table2.dataSource = res.result.records
|
||||
this.table2.selectedRows = []
|
||||
} else {
|
||||
this.$error({title: '子表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
this.table2.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// table2【子表】当选择的行变化时触发的事件
|
||||
handleTable2SelectRowChange(event) {
|
||||
this.handleTableSelectRowChange(this.table2, event)
|
||||
},
|
||||
|
||||
// 当table2【子表】分页参数变化时触发的事件
|
||||
handleTable2PageChange(event) {
|
||||
// 重新赋值
|
||||
this.table2.pagination.current = event.current
|
||||
this.table2.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.loadTable2Data()
|
||||
},
|
||||
|
||||
// 加载table3【孙表】的数据,根据子表的id进行查询
|
||||
loadTable3Data() {
|
||||
// 如果主表没有选择,则不查询
|
||||
let selectedRows = this.table2.selectedRows
|
||||
if (!selectedRows || selectedRows.length === 0) {
|
||||
this.table3.pagination.total = 0
|
||||
this.table3.dataSource = []
|
||||
this.table3.selectedRows = []
|
||||
return
|
||||
} else if (this.table2.lastRow == null) {
|
||||
this.table2.lastRow = selectedRows[selectedRows.length - 1]
|
||||
}
|
||||
let formData = {
|
||||
parentId: this.table2.lastRow.id,
|
||||
pageNo: this.table3.pagination.current,
|
||||
pageSize: this.table3.pagination.pageSize
|
||||
}
|
||||
this.table3.loading = true
|
||||
getAction(this.url.getData, formData).then(res => {
|
||||
if (res.success) {
|
||||
this.table3.pagination.total = res.result.total
|
||||
this.table3.dataSource = res.result.records
|
||||
} else {
|
||||
this.$error({title: '子表查询失败', content: res.message})
|
||||
}
|
||||
}).finally(() => {
|
||||
this.table3.loading = false
|
||||
})
|
||||
},
|
||||
// 当table3【孙表】分页参数变化时触发的事件
|
||||
handleTable3PageChange(event) {
|
||||
// 重新赋值
|
||||
this.table3.pagination.current = event.current
|
||||
this.table3.pagination.pageSize = event.pageSize
|
||||
// 查询数据
|
||||
this.loadTable3Data()
|
||||
},
|
||||
|
||||
/** 公共方法:处理表格选中变化事件 */
|
||||
handleTableSelectRowChange(table, event) {
|
||||
let {row, action, selectedRows, $table} = event
|
||||
// 获取最后一个选中的
|
||||
let lastSelected = selectedRows[selectedRows.length - 1]
|
||||
if (action === 'selected') {
|
||||
table.lastRow = row
|
||||
} else if (action === 'selected-all') {
|
||||
// 取消全选
|
||||
if (selectedRows.length === 0) {
|
||||
table.lastRow = null
|
||||
} else if (!table.lastRow) {
|
||||
table.lastRow = lastSelected
|
||||
}
|
||||
} else if (action === 'unselected' && row === table.lastRow) {
|
||||
table.lastRow = lastSelected
|
||||
}
|
||||
$table.setCurrentRow(table.lastRow)
|
||||
table.selectedRows = selectedRows
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue