<template> <div class="inner-container" :element-loading-text="saveText" > <el-scrollbar class="data-set-scrollbar"> <div class="header"> <el-page-header class="bs-el-page-header"> <template slot="content"> <div class="page-header"> <div class="page-header-left"> {{ !isEdit ? 'JS数据集详情' : dataForm.id ? 'JS数据集编辑' : 'JS数据集新增' }} </div> <div class="page-header-right"> <el-button class="bs-el-button-default" @click="openNewWindow('https://www.yuque.com/chuinixiongkou/bigscreen/groovy_dataset')" > 帮助 </el-button> <el-button v-if="isEdit" type="primary" @click="save('form')" > 保存 </el-button> <el-button class="bs-el-button-default" @click="goBack" > 返回 </el-button> </div> </div> </template> </el-page-header> </div> <el-row style="margin: 16px 16px 0;"> <el-col :span="isEdit ? 16 : 24"> <el-form ref="form" :model="dataForm" :rules="rules" label-width="120px" style="padding: 16px 16px 0;" class="bs-el-form" > <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="名称" prop="name" > <el-input v-model="dataForm.name" class="bs-el-input" clearable :disabled="!isEdit" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="分组" prop="typeId" > <el-select ref="selectParentName" v-model="dataForm.typeId" class="bs-el-select" popper-class="bs-el-select" clearable :disabled="!isEdit" @clear="clearType" @visible-change="setCurrentNode" > <el-option style="height: auto;padding: 0;" :label="typeName" :value="dataForm.typeId" > <div class="tree-box"> <el-tree ref="categorySelectTree" :data="categoryData" node-key="id" :indent="0" :props="{ label: 'name', children: 'children' }" :default-expand-all="true" :highlight-current="true" :expand-on-click-node="false" class="bs-el-tree" @node-click="selectParentCategory" > <span slot-scope="{ data }" class="custom-tree-node" > <span> <i :class="data.children && data.children.length ? 'el-icon el-icon-folder' : 'el-icon el-icon-document'" /> {{ data.name }} </span> </span> </el-tree> </div> </el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="描述" prop="remark" > <el-input v-model="dataForm.remark" class="bs-el-input" :disabled="!isEdit" /> </el-form-item> </el-col> </el-row> </el-form> <div v-if="isEdit" class="sql-config" > <div> <codemirror ref="targetInSql" v-model="dataForm.config.script" :options="codemirrorOption" style="margin-top: 2px" /> </div> <div style="text-align: center; padding: 16px 0;"> <el-button type="primary" @click="scriptExecute()" > 执行 </el-button> </div> </div> </el-col> <el-col v-if="isEdit" :span="8" > <div class="right-setting"> <div class="paramConfig"> <div class="title-style bs-title-style"> 动态参数 <el-button type="text" style="float: right;border: none;margin-top: -4px;" @click="$refs.paramsSettingDialog.open()" > 配置 </el-button> </div> <div class="field-wrap bs-field-wrap bs-scrollbar"> <div v-for="param in dataForm.config.paramsList" :key="param.name" class="field-item" @click="$refs.paramsSettingDialog.open()" > <span>{{ param.name }}</span> <span v-show="param.remark" style="color: #909399;" > ({{ param.remark }}) </span> <el-button class="edit_field" type="text" style="float: right;border: none;margin-top: 2px;" @click="$refs.paramsSettingDialog.open()" > 配置 </el-button> </div> </div> </div> <div class="structure"> <div class="title-style bs-title-style"> 输出字段 <el-button type="text" style="float: right;border: none;margin-top: -4px;" @click="$refs.outputFieldDialog.open()" > 配置 </el-button> </div> <div class="field-wrap bs-field-wrap bs-scrollbar"> <div v-for="(field, key) in outputFieldList" :key="key" class="field-item" @click="$refs.outputFieldDialog.open()" > <span>{{ field.fieldName }}</span> <span v-show="field.fieldDesc" style="color: #909399;" > ({{ field.fieldDesc }})</span> <el-button class="edit_field" type="text" style="float: right;border: none;margin-top: 2px;" @click="$refs.outputFieldDialog.open()" > 配置 </el-button> </div> </div> </div> </div> </el-col> </el-row> <div v-if="isEdit" class="dataPreView" style="margin-top: 12px;" > <div class="result-view"> 数据预览 </div> <div v-loading="tableLoading" class="bs-table-box is-Edit bs-scrollbar" > <el-table align="center" :data="dataPreviewList" max-height="400" :border="true" class="bs-el-table bs-scrollbar" > <el-table-column v-for="(value, key) in dataPreviewList[0]" :key="key" :label="key" align="center" show-overflow-tooltip :render-header="renderHeader" > <template slot-scope="scope"> <span>{{ scope.row[key] }}</span> </template> </el-table-column> </el-table> </div> </div> <div v-if="!isEdit" class="dataPreView" > <el-tabs v-model="activeName"> <el-tab-pane v-loading="tableLoading" label="数据预览" name="data" > <div class="bs-table-box"> <el-table align="center" :data="dataPreviewList" max-height="400" :border="true" class="bs-el-table" > <el-table-column v-for="(value, key) in dataPreviewList[0]" :key="key" :label="key" align="center" show-overflow-tooltip :render-header="renderHeader" > <template slot-scope="scope"> <span>{{ scope.row[key] }}</span> </template> </el-table-column> </el-table> </div> </el-tab-pane> <el-tab-pane v-loading="tableLoading" label="数据集结构" name="structure" > <div class="bs-table-box"> <el-table max-height="400" :data="outputFieldList" :border="true" align="center" > <el-table-column align="center" show-overflow-tooltip prop="fieldName" label="字段值" /> <el-table-column align="center" prop="fieldDesc" label="字段描述" > <template slot-scope="scope"> <el-input v-if="isEdit" v-model="scope.row.fieldDesc" size="small" class="labeldsc bs-el-input" /> <span v-else>{{ scope.row.fieldDesc }}</span> </template> </el-table-column> </el-table> </div> </el-tab-pane> </el-tabs> </div> <ParamsSettingDialog ref="paramsSettingDialog" :params-list="dataForm.config.paramsList" @saveParams="saveParams" /> <OutputFieldDialog ref="outputFieldDialog" :output-field-list="outputFieldList" @setFieldList="(list) => { outputFieldList = list }" /> </el-scrollbar> <FieldFillDialog ref="fieldFillDialog" @fieldDescFill="fieldDescFill" @fieldDescEdit="fieldDescEdit" @toSave="toSave" /> </div> </template> <script> import ParamsSettingDialog from './ParamsSettingDialog.vue' import OutputFieldDialog from './OutputFieldDialog.vue' import FieldFillDialog from './FieldFillDialog.vue' import { nameCheckRepeat, datasetAdd, datasetUpdate, getDataset, getCategoryTree } from 'data-room-ui/js/utils/datasetConfigService' import { codemirror } from 'vue-codemirror' import 'codemirror/mode/javascript/javascript' import 'codemirror/lib/codemirror.css' import 'codemirror/theme/nord.css' export default { name: 'JsDataSet', components: { codemirror, FieldFillDialog, ParamsSettingDialog, OutputFieldDialog }, props: { config: { type: Object, default: () => { } }, isEdit: { type: Boolean, default: false }, datasetId: { type: String, default: null }, typeId: { type: String, default: '' }, appCode: { type: String, default: '' } }, data () { const validateName = (rule, value, callback) => { nameCheckRepeat({ id: this.datasetId, name: value, moduleCode: this.appCode }).then((r) => { if (r) { callback(new Error('数据集名称已存在')) } else { callback() } }) } return { dataForm: { id: '', name: '', typeId: '', remark: '', config: { script: '', paramsList: [] } }, rules: { name: [ { required: true, message: '请输入数据集名称', trigger: 'blur' }, { validator: validateName, trigger: 'blur' } ] }, codemirrorOption: { mode: 'text/javascript', lineNumbers: true, lineWrapping: true, theme: 'nord', extraKey: { Ctrl: 'autocomplete' }, hintOptions: { completeSingle: true } }, activeName: 'data', dataPreviewList: [], outputFieldList: [], structurePreviewListCopy: [], typeName: '', categoryData: [], // fieldDescVisible: false, fieldsetVisible: false, paramsVisible: false, tableLoading: false, saveloading: false, saveText: '', // paramsListCopy: [], isSet: false, // 参数是否配置状态 passTest: false, fieldDesc: null // 字段描述 } }, watch: { 'dataForm.config.script' (val) { if (!val) { this.passTest = false } } }, mounted () { this.init() }, methods: { async init () { this.categoryData = await getCategoryTree({ tableName: 'dataset', moduleCode: this.appCode }) if (this.typeId) { this.dataForm.typeId = this.typeId this.$nextTick(() => { try { this.typeName = this.$refs.categorySelectTree.getNode(this.dataForm.typeId).data.name } catch (error) { console.error(error) } }) } if (this.datasetId) { getDataset(this.datasetId).then(res => { const { id, name, typeId, remark, config } = res const { script, paramsList, fieldDesc, fieldList } = config this.dataForm = { id, name, typeId, remark, config: { script, paramsList } } this.fieldDesc = fieldDesc this.outputFieldList = fieldList this.scriptExecute(true) }) } }, // 保存数据集 save (formName, nochecktosave = false) { if (this.passTest === false) { this.$message.error('请确保脚本不为空且执行通过') return } if (!this.outputFieldList.length) { this.$message.warning('该执行脚本未生成输出字段,请重新检查') return } if (!nochecktosave) { const temp = this.outputFieldList.some(item => { return item.fieldDesc === '' || !item.hasOwnProperty('fieldDesc') }) // true-存在为空 if (temp) { this.$refs.fieldFillDialog.open() // this.fieldDescVisible = true return } } this.$refs[formName].validate((valid) => { if (valid) { this.saveloading = true this.saveText = '正在保存...' const { datasetId, dataForm, config, appCode, fieldDesc, outputFieldList } = this const form = { id: datasetId, name: dataForm.name, typeId: dataForm.typeId, remark: dataForm.remark, datasetType: config.datasetType, moduleCode: appCode, editable: appCode ? 1 : 0, config: { className: config.className, script: dataForm.config.script, fieldDesc, paramsList: dataForm.config.paramsList, fieldList: outputFieldList } } const datasetSave = this.dataForm.id === '' ? datasetAdd : datasetUpdate datasetSave(form).then(() => { this.$message.success('操作成功') this.$parent.init(false) this.$parent.setType = null this.saveloading = false this.saveText = '' this.goBack() }).catch(() => { this.saveloading = false this.saveText = '' }) } }) }, saveParams (val) { this.dataForm.config.paramsList = val }, // 取消操作 // cancelField () { // this.structurePreviewListCopy = cloneDeep(this.outputFieldList) // this.fieldsetVisible = false // }, // 设置输出字段 setField () { // this.outputFieldList = cloneDeep(this.structurePreviewListCopy) // if (this.outputFieldList.length) { // this.fieldDesc = {} // this.outputFieldList.forEach(key => { // this.fieldDesc[key.fieldName] = key.fieldDesc // }) // } else { // this.fieldDesc = null // } // this.fieldsetVisible = false }, // 字段值填充 fieldDescFill () { this.fieldDesc = {} this.outputFieldList.forEach(field => { if (field.fieldDesc === '' || !field.hasOwnProperty('fieldDesc')) { field.fieldDesc = field.fieldName this.fieldDesc[field.fieldName] = field.fieldName } else { this.fieldDesc[field.fieldName] = field.fieldDesc } }) this.save('form') this.$refs.fieldFillDialog.close() // this.fieldDescVisible = false }, // 进入编辑 fieldDescEdit () { this.$refs.fieldFillDialog.close() // this.fieldDescVisible = false this.fieldsetVisible = true }, // 继续保存 toSave () { this.fieldDesc = {} this.outputFieldList.forEach(field => { this.fieldDesc[field.fieldName] = field.fieldDesc }) this.save('form', true) this.$refs.fieldFillDialog.close() // this.fieldDescVisible = false }, // 字段描述构建及同步 buildFieldDesc () { const fieldDesc = {} this.outputFieldList.forEach(field => { if (this.fieldDesc.hasOwnProperty(field.fieldName)) { field.fieldDesc = this.fieldDesc[field.fieldName] } fieldDesc[field.fieldName] = field.fieldDesc }) this.fieldDesc = fieldDesc }, // 脚本执行 scriptExecute (isInit = false) { if (this.dataForm.config.script) { const javascript = this.dataForm.config.script let scriptMethod = null try { const scriptAfterReplacement = javascript.replace(/\${(.*?)}/g, (match, p) => { return `'${this.dataForm.config.paramsList.find(param => param.name === p).value}'` }) // eslint-disable-next-line no-new-func scriptMethod = new Function(scriptAfterReplacement) } catch (error) { this.passTest = false this.$message.error(`脚本执行错误,请检查脚本,具体错误:${error}`) return } // 调用方法生成随机数据 const returnResult = scriptMethod() // 检查数据是否为数组 if (!Array.isArray(returnResult)) { this.passTest = false this.$message.error('脚本执行结果不是数组,请检查脚本') return } const keys = [] returnResult.forEach(item => { Object.keys(item).forEach(key => { if (!keys.includes(key)) { keys.push(key) } }) }) if (this.outputFieldList.length === 0) { this.outputFieldList = keys.map(item => { return { fieldName: item, fieldDesc: '' } }) } // 如果脚本有变化,生成的keys和outputFieldList的长度不一致,就重新生成outputFieldList,仅删除或添加变化的那个字段,其余的不变化 if (this.outputFieldList.length !== keys.length) { const newOutputFieldList = [] keys.forEach(key => { const field = this.outputFieldList.find(item => item.fieldName === key) if (field) { newOutputFieldList.push(field) } else { newOutputFieldList.push({ fieldName: key, fieldDesc: '' }) } }) this.outputFieldList = newOutputFieldList } // 如果脚本有变化,生成的keys和outputFieldList的长度一致,仅字段名变化了,就重新生成outputFieldList if (this.outputFieldList.length === keys.length) { const newOutputFieldList = [] keys.forEach(key => { const field = this.outputFieldList.find(item => item.fieldName === key) if (field) { newOutputFieldList.push(field) } else { newOutputFieldList.push({ fieldName: key, fieldDesc: '' }) } }) this.outputFieldList = newOutputFieldList } // 如果有字段描述,就同步 if (this.outputFieldList.length && this.fieldDesc && !isInit) { this.buildFieldDesc() } // 如果有数据,就通过测试 if (this.outputFieldList.length > 0) { // this.structurePreviewListCopy = cloneDeep(this.outputFieldList) this.dataPreviewList = returnResult this.passTest = true if (!isInit) { this.$message.success('脚本执行通过') } } else { this.passTest = false } } else { this.passTest = false this.$message.error('请填写脚本') } }, // 执行事件 // toExecute () { // if (this.dataForm.config.paramsList.length) { // this.isSet = false // this.paramsVisible = true // } else { // 无参数,直接执行脚本 // this.scriptExecute() // } // }, // 清空分类 clearType () { this.typeName = '' this.dataForm.typeId = '' }, // 分类展开高亮 setCurrentNode ($event) { if ($event) { const key = this.dataForm.typeId || null this.$refs.categorySelectTree.setCurrentKey(key) } }, // openParamsSetting () { // this.$refs.paramsSettingDialog.open() // }, // 分类选择 selectParentCategory (value) { this.dataForm.typeId = value.id this.typeName = value.name this.$refs.selectParentName.blur() }, goBack () { this.$emit('back') }, renderHeader (h, { column, index }) { const labelLong = column.label.length // 表头label长度 const size = 14 // 根据需要定义标尺,直接使用字体大小确定就行,也可以根据需要定义 column.minWidth = labelLong * size < 120 ? 120 : labelLong * size // 根据label长度计算该表头最终宽度 return h('span', { class: 'cell-content', style: { width: '100%' } }, [column.label]) }, openNewWindow (url) { window.open(url, '_blank') } } } </script> <style lang="scss" scoped> @import '../../../packages/assets/style/bsTheme.scss'; .data-set-scrollbar { height: 100%; overflow-y: auto; overflow-x: none; .el-scrollbar__view { height: 100%; } } ::v-deep .el-input__inner { width: 100% !important; } .page-header { display: flex; position: relative; .page-header-right { position: absolute; right: 16px; } } .sql-config { padding: 0 16px; } .operation { ::v-deep .el-select { width: 200px !important; margin-right: 16px; } display: flex; } ::v-deep .CodeMirror { height: 180px !important; font-family: Helvetica, Tahoma; } .no-border { border: 0; } ::v-deep .fieldDescCheck { .el-dialog__body { height: fit-content !important; min-height: unset !important; } } .title-style { padding: 8px 12px; background-color: #f6f7fb; border-left: 5px solid var(--bs-el-color-primary); margin: 16px 16px 0 0; } .field-wrap { // max-height: 110px; overflow: auto; margin-right: 16px; cursor: pointer; .field-item { line-height: 32px; padding: 0 12px 0 16px; .edit_field { display: none; } &:hover { background-color: #f2f7fe; .edit_field { display: block; } } } } .right-setting { height: 358px; overflow: hidden; display: flex; flex-direction: column; .paramConfig { max-height: 179px; .field-wrap { max-height: 127px; } } .structure { flex: 1; overflow: hidden; .field-wrap { height: calc(100% - 40px); } } } .result-view { font-size: 14px; font-weight: 600; color: var(--bs-el-text); position: relative; padding: 16px 0; padding-left: 12px; border-bottom: 1px solid var(--bs-background-1); &::before { content: ""; height: 14px; position: absolute; left: 0; top: 50%; transform: translateY(-50%); border-left: 4px solid var(--bs-el-color-primary); } } ::v-deep .bs-table-box.is-Edit .el-table { max-height: unset !important; .el-table__body-wrapper { max-height: unset !important; } } .bs-table-box { padding: 0; height: 100% !important; margin-bottom: 0 !important; } .tree-box { padding: 0; } </style>