vue2<el-input>不需要自动四舍五入、支持复制粘贴

                                                                              目录

1、项目场景:

2、问题描述

3、原因分析:

4、解决方案:

5、复制、黏贴功能


1、项目场景

场景一:输入数据时,不需要自动四舍五入,用户输入什么就显示什么。

场景二:多列、多行填写数据时,且重复数据多、校验多时,需要快速复制、粘贴数据。


2、问题描述

<el-input>或<el-input-number>输入数值时,会默认自动四舍五入,项目中遇到场景要求在保留位数前提下,用户输入什么就显示什么,真TM沃特法克!!!(^__^)

上面这种输入后,显示的值就不符合要求。 


3、原因分析

element-ui 默认是四舍五入的。


4、场景一:解决方案

通过主要代码控制

 blurInput方法
 oninput ="this.value = this.value.replace(/[^-\d]/g, '')"
 @keyup.enter="handleEnter(scope.row.index)"

对数据输入、enter、失焦进行控制,要考虑输入.  .2  ..3  -2-   -2--  等情况。

确保输入三位小数时:例如3.887,输入3.8876,不会四舍五入,只显示3.887,且传给后端是3.887。

<template>
  <el-table :data="tableData" ref="tableData" class="customer-table"  @cell-click="tableDbEdit" :row-class-name="tableRowClassName">
    <el-table-column label="序号" width="50px" type="index" header-align="center" align="center" class="custom-index-column" :resizable="false"/>
    <el-table-column prop="packageMonth" label="月份" width="100px" show-overflow-tooltip align="center"  header-align="center" :resizable="false"/>
    <el-table-column  label="常规数据" header-align="center" key="1" :sortable="false">
      <el-table-column prop="u11" label="三位小数" min-width="80px" header-align="center" align="right" key="u11" :sortable="false">
        <template scope="scope">
          <el-input  v-if="showInput == `u11${scope.row.index}`"  v-model="scope.row.u11" placeholder=""
                     @blur='blurInput(scope.row.guid,"u11",scope.row.u11,"1",scope.row.packageMonth)' v-focus
                     oninput ="this.value=this.value.replace(/[^0-9.]/g,'')"
                     onkeyup="this.value=this.value.match(/\d+\.?\d{0,3}/);" clearable></el-input>
          <div v-else>{{ scope.row.u11 |thereRounding }}</div>
        </template>
      </el-table-column>

      <el-table-column prop="commonPrice" label="两位小数" :sortable="false"
                       min-width="80px" key="commonPrice" header-align="center" align="right">
        <template scope="scope">
          <el-input  v-if="showInput == `commonPrice${scope.row.index}`"  v-model="scope.row.commonPrice" placeholder=""
                     @blur='blurInput(scope.row.guid,"commonPrice",scope.row.commonPrice,"3",scope.row.packageMonth)' v-focus
                     oninput ="this.value=this.value.replace(/[^0-9.]/g,'')"
                     onkeyup="this.value=this.value.match(/\d+\.?\d{0,2}/);" clearable></el-input>
          <div v-else>{{ scope.row.commonPrice |twoRounding}}</div>
        </template>
      </el-table-column>
      <el-table-column  prop="commonPriceDifference" label="整数"
                       min-width="80px" key="commonPriceDifference" header-align="center" align="right" :sortable="false">
        <template scope="scope">
          <el-input  v-if="showInput == `commonPriceDifference${scope.row.index}`"  v-model="scope.row.commonPriceDifference" placeholder=""
                     @blur='blurInput(scope.row.guid,"commonPriceDifference",scope.row.commonPriceDifference,"2",scope.row.packageMonth)' v-focus
                     oninput ="this.value = this.value.replace(/[^-\d]/g, '')"
                     @keyup.enter="handleEnter(scope.row.index)" clearable></el-input>
          <div v-else>{{ scope.row.commonPriceDifference|twoRounding }}</div>
        </template>
      </el-table-column>
      <el-table-column :sortable="false" align="center" text-align="center" width="90" label="操作">
        <template v-slot="scope">
          <el-button type="text" @click="copy(scope.row)" style="font-size: 12px">复制</el-button>
          <el-button type="text" @click="paste(scope.row)" style="font-size: 12px">粘贴</el-button>
        </template>
      </el-table-column>
    </el-table-column>
  </el-table>
</template>

<script>
    export default {
      name: "index",
      directives: {
        // 通过自定义指令实现的表单自动获得光标的操作
        //注意:唯一的主键guid不能一样,否则有错,光标无法点进去
        focus: {
          inserted: function (el) {
            //debugger;
            if (el.tagName.toLocaleLowerCase() == 'input') {
              el.focus()
            } else {
              if (el.getElementsByTagName('input')) {
                el.getElementsByTagName('input')[0].focus()
              }
            }
            el.focus()
          }
        }
      },
      filters: {
        // 保留2位小数
        twoRounding(value) {
          if (value != null && value !== '') {
            return (value * 1).toFixed(2)
          }
        },
        thereRounding(value) {
          if (value != null && value !== '') {
            return (value * 1).toFixed(3)
          }
        },
        integerRounding(value) {
          if (value != null && value !== '') {
            return (value * 1).toFixed(0)
          }
        },
      },
      data() {
        return {
          showInput:'',
          tableData: [{
            "packageMonth": "一月",
            "packageType": null,
            "packageCategory": null,
            "commonPrice": undefined,
            "commonPriceDifference": undefined,
            "greenPrice": undefined,
            "greenPriceDifference": undefined,
            "greenEnvironmentPrice": undefined,
            "greenEnvironmentPricePay": undefined,
          },{
            "packageMonth": "二月",
            "packageType": null,
            "packageCategory": null,
            "commonPrice": undefined,
            "commonPriceDifference": undefined,
            "greenPrice": undefined,
            "greenPriceDifference": undefined,
            "greenEnvironmentPrice": undefined,
            "greenEnvironmentPricePay": undefined,
          }
          ],
          copyObject: {},//复制的内容
        }
      },
      methods: {
        tableRowClassName({row, rowIndex}) {
          row.index = rowIndex
        },
        tableDbEdit(row, column, cell, event) {
          this.showInput = column.property + row.index
          console.log('this.showInput:' + this.showInput)
        },
        truncateDecimal(num, decimals) {
          const parts = num.toString().split('.');
          if (parts.length < 2) return num.toString();   // 如果没有小数部分,直接返回
          parts[1] = parts[1].slice(0, decimals);        // 截取小数点后的位数
          return parts.join('.');
        },
        handleEnter(index,name) {
          const inputValue = this.tableData[index].name;
          if (isNaN(inputValue)) {
            if (inputValue !== '' && inputValue !== '-') {
              this.$set(this.tableData, `${index}.name`, ''); // 清空非数字且不是负号或空字符串时清空
            }
          } else {
            // 确保输入的是整数,如果用户输入了小数则转换为整数
            const parsedValue = parseInt(inputValue, 10);
            if (parsedValue.toString() !== inputValue) {
              this.$set(this.tableData, `${index}.name`, parsedValue);
            }
          }
        },
        async blurInput(id, name, value,flag,currMoth) { // 当input失去光标后进行的操作
          // console.log("===1===>"+JSON.stringify(this.tableData))
          if (value !== '' && value != null) {
            this.tableData.forEach(item => {
              if (item.packageMonth == currMoth) {      //当月数据
                if (value.toString().startsWith('.')) {
                  if (name == 'commonPrice' || name == 'greenPrice' || name == 'u11' || name == 'u12' || name == 'u21' || name == 'u22'
                    || name == 'greenEnvironmentPrice' || name == 'greenEnvironmentPricePay') {//当以.开始置为null
                    value = null
                    item[name] = null
                  }
                } else {
                  const firstDotIndex = value.toString().indexOf('.');
                  const secondDotIndex = value.toString().indexOf('.', firstDotIndex + 1);
                  let cleanedValue = value;
                  if (firstDotIndex !== -1 && secondDotIndex !== -1) {      // 存在两个小数点,则保留第二个小数点前的所有信息
                    cleanedValue = value.substring(0, secondDotIndex);
                  }
                  if (cleanedValue !== value) {
                    value = cleanedValue;
                  }
                  if (flag == '1') { //保留三位 1—保留三位  2-保留整数 3-保留两位
                    item[name] = this.truncateDecimal(value, 3)
                  } else if (flag == '2') {
                    if (isNaN(Number(value)) || value == '') {//处理 输入-  -2- -2--等情况
                      item[name] = null
                    } else {
                      item[name] = this.truncateDecimal(value, 0)
                    }
                  } else {
                    item[name] = this.truncateDecimal(value, 2)
                  }
                }
              }
            })
          }
          this.showInput = ''
        },
        //复制文本内容
        copy(row) {
          this.copyObject = []
          Object.keys(row).forEach(key => {
            this.copyObject[key] = row[key]
          })
          // console.log("------2--------> "+JSON.stringify(row));
        },
        //粘贴文本内容
        paste(row) {
          var month = ''
          var index = ''
          for (let i = 0; i < this.tableData.length; i++) {
            if (row.packageMonth === this.tableData[i].packageMonth) {
              month = this.tableData[i].packageMonth   //粘贴时,保持当前行的数据 月份和index不变
              index = this.tableData[i].index
              Object.keys(this.copyObject).forEach(key => {
                this.$set(this.tableData[i], key, this.copyObject[key])
              })
              this.$set(this.tableData[i], "packageMonth", month)  //先复制,再替换成原有的 月份 和 index
              this.$set(this.tableData[i], "index", index)
            }
          }
        },
      }
    }
</script>

<style scoped>

</style>

5、场景二:复制、黏贴功能

 

场景:

      针对列比较多、或需求要复制、粘贴时。

主要源码

     复制:定义变量、复制数据;

     粘贴:对比数据、获取不需要改变的列的值、全部复制数据,还原不需要改变的列。

       //复制文本内容
        copy(row) {
          this.copyObject = []
          Object.keys(row).forEach(key => {
            this.copyObject[key] = row[key]
          })
          // console.log("------2--------> "+JSON.stringify(row));
        },
        //粘贴文本内容
        paste(row) {
          var month = ''
          var index = ''
          for (let i = 0; i < this.tableData.length; i++) {
            if (row.packageMonth === this.tableData[i].packageMonth) {
              month = this.tableData[i].packageMonth   //粘贴时,保持当前行的数据 月份和index不变
              index = this.tableData[i].index
              Object.keys(this.copyObject).forEach(key => {
                this.$set(this.tableData[i], key, this.copyObject[key])
              })
              this.$set(this.tableData[i], "packageMonth", month)  //先复制,再替换成原有的 月份 和 index
              this.$set(this.tableData[i], "index", index)
            }
          }
        },

<template> <el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" :rules="rules" label-width="auto" > <el-form-item label="产品名称" prop="name"> <el-input v-model="ruleForm.name" /> </el-form-item> <el-form-item label="产品描述" prop="description"> <el-input v-model="ruleForm.description" type="textarea" /> </el-form-item> <el-form-item label="产品描述" prop="description"> <el-input v-model="ruleForm.description" /> </el-form-item> <el-form-item label="Activity zone" prop="region"> <el-select v-model="ruleForm.region" placeholder="Activity zone"> <el-option label="Zone one" value="shanghai" /> <el-option label="Zone two" value="beijing" /> </el-select> </el-form-item> <el-form-item label="Activity count" prop="count"> <el-select-v2 v-model="ruleForm.count" placeholder="Activity count" :options="options" /> </el-form-item> <el-form-item label="Activity time" required> <el-col :span="11"> <el-form-item prop="date1"> <el-date-picker v-model="ruleForm.date1" type="date" aria-label="Pick a date" placeholder="Pick a date" style="width: 100%" /> </el-form-item> </el-col> <el-col class="text-center" :span="2"> <span class="text-gray-500">-</span> </el-col> <el-col :span="11"> <el-form-item prop="date2"> <el-time-picker v-model="ruleForm.date2" aria-label="Pick a time" placeholder="Pick a time" style="width: 100%" /> </el-form-item> </el-col> </el-form-item> <el-form-item label="Instant delivery" prop="delivery"> <el-switch v-model="ruleForm.delivery" /> </el-form-item> <el-form-item label="Activity location" prop="location"> <el-segmented v-model="ruleForm.location" :options="locationOptions" /> </el-form-item> <el-form-item label="Activity type" prop="type"> <el-checkbox-group v-model="ruleForm.type"> <el-checkbox value="Online activities" name="type"> Online activities </el-checkbox> <el-checkbox value="Promotion activities" name="type"> Promotion activities </el-checkbox> <el-checkbox value="Offline activities" name="type"> Offline activities </el-checkbox> <el-checkbox value="Simple brand exposure" name="type"> Simple brand exposure </el-checkbox> </el-checkbox-group> </el-form-item> <el-form-item label="Resources" prop="resource"> <el-radio-group v-model="ruleForm.resource"> <el-radio value="Sponsorship">Sponsorship</el-radio> <el-radio value="Venue">Venue</el-radio> </el-radio-group> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm(ruleFormRef)"> Create </el-button> <el-button @click="resetForm(ruleFormRef)">Reset</el-button> </el-form-item> </el-form> </template> <script setup> import productApi from '@/api/products' import { ref, reactive } from 'vue'; const ruleFormRef = ref<FormInstance>() const ruleForm = reactive<RuleForm>({ name: '', description:'', region: '', count: '', date1: '', date2: '', delivery: false, location: '', type: [], resource: '', }) const locationOptions = ['Home', 'Company', 'School'] const rules = reactive<FormRules<RuleForm>>({ name: [ { required: true, message: '请输入商品名称', trigger: 'blur' }, { max: 10, message: '长度超过10位', trigger: 'blur' }, ], description: [ { required: true, message: '请输入商品描述', trigger: 'blur' }, { max: 200, message: '长度超过200位', trigger: 'blur' }, ], region: [ { required: true, message: 'Please select Activity zone', trigger: 'change', }, ], count: [ { required: true, message: 'Please select Activity count', trigger: 'change', }, ], date1: [ { type: 'date', required: true, message: 'Please pick a date', trigger: 'change', }, ], date2: [ { type: 'date', required: true, message: 'Please pick a time', trigger: 'change', }, ], location: [ { required: true, message: 'Please select a location', trigger: 'change', }, ], type: [ { type: 'array', required: true, message: 'Please select at least one activity type', trigger: 'change', }, ], resource: [ { required: true, message: 'Please select activity resource', trigger: 'change', }, ], }) const submitForm = async (formEl) => { if (!formEl) return await formEl.validate((valid, fields) => { if (valid) { console.log('submit!') } else { console.log('error submit!', fields) } }) } const resetForm = (formEl) => { if (!formEl) return formEl.resetFields() } const options = Array.from({ length: 10000 }).map((_, idx) => ({ value: `${idx + 1}`, label: `${idx + 1}`, })) //产品分类 function getCategory(){ productApi.getCategory().then(res => { categorys.value = res.data; }) }  哪错了 </script>
07-11
如下述代码1,是一个弹窗的代码页面(如图1所示),调整一下左侧的"SQL查询",当分辨率高变形的有点厉害了,请参照代码2中的样式所示,请优化"SQL查询"的样式,并完整的写出修改后的代码(改变代码1中的功能,即"sql查询"框中能够输入sql语句变) ### 代码1:src\views\handle\dataset\add\AddView.vue ```vue <template> <div class="sql-dataset-container"> <el-container class="layout-container"> <!-- 右侧SQL配置表单 --> <el-aside width="40%" class="config-aside"> <div class="config-section"> <div class="section-header"> <h2><el-icon><setting /></el-icon> SQL数据集配置</h2> </div> <el-form :model="form" :rules="rules" label-position="top" class="config-form" ref="formRef" > <!-- 数据集名称 - 修改为行内布局 --> <el-form-item v-if="props.id===''" label="数据集名称" prop="name" class="form-item-card inline-form-item"> <el-input v-model="form.name" placeholder="例如: 用户行为分析数据集" size="large" :prefix-icon="Document" /> </el-form-item> <!-- SQL编辑器 --> <el-form-item label="SQL查询" prop="sql" class="form-item-card sql-editor-item"> <div class="sql-editor-container"> <VAceEditor v-model:value="form.sql" lang="sql" theme="github" style="height: 300px; width: 100%" :options="{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, highlightActiveLine: true, showLineNumbers: true, tabSize: 2, }" /> <div class="sql-tips"> <el-tag type="info" size="small"> <el-icon><info-filled /></el-icon> 提示: 请确保SQL语法正确且符合数据源规范 </el-tag> </div> </div> </el-form-item> </el-form> </div> </el-aside> <!-- 左侧数据预览 --> <el-main class="preview-main"> <div class="preview-section"> <div class="section-header"> <div class="header-left"> <h2><el-icon><data-line /></el-icon> 数据预览</h2> </div> <div class="header-right"> <el-button type="warning" plain size="small" @click="emit('cancel')" :icon="Close">取消</el-button> <el-button type="success" plain size="small" :disabled="!form.sql" @click="fetchPreviewData" :icon="Refresh">执行SQL预览</el-button> <el-button type="primary" plain size="small" @click="handleSave" :icon="Check">保存</el-button> </div> </div> <div class="preview-content"> <zr-table :tableModule="tableModule" /> </div> </div> </el-main> </el-container> </div> </template> <script setup> import {ref, reactive, onMounted, getCurrentInstance} from 'vue' import { VAceEditor } from 'vue3-ace-editor' import '@/components/CodeEdit/ace-config.js' import { Setting, DataLine, Document, Refresh, Check, Close, InfoFilled, Edit, Download } from '@element-plus/icons-vue' import ZrTable from "@/components/ZrTable/index.vue"; import {buildFilterSos} from "@/components/ZrTable/table.js"; import { handleDataSetCreate, handleDataSetGet, handleDataSetPreviewData, handleDataSetReconstruct } from "@/api/handle/dataset.js"; const { proxy } = getCurrentInstance() const props = defineProps({ sceneId:{ type: String, default: '', }, id:{ type: String, default: '', } }) const emit = defineEmits(['saved', 'cancel']) const form = ref({ id:props.id, name: '', sceneId:props.sceneId, type:'view', sql: '', info:'', }) //数据预览 const columnsData= ref([]) const queryData = ref([]) const state = reactive({ columns: columnsData, // 表格配置 query: queryData, // 查询条件配置 queryForm: {}, // 查询form表单 loading: false, // 加载状态 dataList: [], // 列表数据 pages:false, }) const { loading, dataList, columns, pages, query, queryForm } = toRefs(state) const formRef = ref(null) // 预览数据 // 传给子组件的 const tableModule = ref({ callback: fetchPreviewData, // 回调,子组件中可以看到很多调用callback的,这里对应的是获取列表数据的方法 // 以下说了,上面都给解释了 queryForm, columns, dataList, loading, pages, query, }) const rules = reactive({ name: [ { required: true, message: '请输入数据集名称', trigger: 'blur' }, { max: 50, message: '名称长度能超过50个字符', trigger: 'blur' } ], sql: [ { required: true, message: '请输入SQL查询语句', trigger: 'blur' }, { validator: (rule, value, callback) => { if (!value || !value.trim()) { callback(new Error('SQL能为空')) } else if (!value.toLowerCase().includes('select')) { callback(new Error('SQL必须是SELECT查询语句')) } else { callback() } }, trigger: 'blur' } ] }) async function fetchPreviewData() { state.loading = true const valid = await formRef.value.validate() if (!valid) { proxy.$modal.msgError("校验错误,请修改好再次执行") return } // 掉自己的接口,切勿复制粘贴 handleDataSetPreviewData({ sql:form.value.sql }).then((res) => { if (res.success) { if(res.data.pageData.length>0){ for (let key in res.data.pageData[0]) { columnsData.value.push({prop: key, label:key, align: 'center',show:1}) } } state.dataList = res.data.pageData proxy.$modal.msgSuccess('SQL执行成功') } else { proxy.$modal.msgError(res.message) } }) state.loading = false } const handleSave = async () => { const valid = await formRef.value.validate() if (!valid) return form.value.info=JSON.stringify({ sql:form.value.sql }) const method=form.value.id===""?handleDataSetCreate:handleDataSetReconstruct method(form.value).then((res) => { if (res.success) { emit('saved',{id: res.data }) } else { proxy.$modal.msgError(res.message) } }) } watch(() => props.id, (newid, oldid) => { if (newid !== oldid && newid!=="") { // 引用变化时触发 handleDataSetGet(newid).then((res) => { if(res.success){ form.value.sql=JSON.parse(res.data.info)?.sql; } }) } }, { immediate: true } // 注意:要用 deep: true ); </script> <style scoped lang="scss"> .sql-dataset-container { height: calc(90vh - 60px); padding: 20px; background-color: #f5f7fa; } .layout-container { height: 100%; background: #fff; border-radius: 12px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05); overflow: hidden; display: flex; } .config-aside { background: #f9fafc; border-right: 1px solid var(--el-border-color-light); padding: 24px; display: flex; flex-direction: column; } .preview-main { padding: 24px; background: #fff; display: flex; flex-direction: column; } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; h2 { margin: 0; color: var(--el-text-color-primary); font-weight: 600; font-size: 18px; display: flex; align-items: center; gap: 8px; } .header-left, .header-right { display: flex; align-items: center; } } .preview-content { flex: 1; border: 1px solid var(--el-border-color-light); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; } .config-form { flex: 1; display: flex; flex-direction: column; :deep(.el-form-item) { margin-bottom: 20px; &.inline-form-item { :deep(.el-form-item__label) { display: inline-flex; align-items: center; width: auto; margin-right: 12px; padding-bottom: 0; } :deep(.el-form-item__content) { display: inline-flex; flex: 1; } } .el-form-item__label { font-weight: 500; padding-bottom: 8px; color: var(--el-text-color-regular); font-size: 14px; } } } .form-item-card { background: #fff; padding: 16px; border-radius: 8px; border-left: 3px solid var(--el-color-primary); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05); } .sql-editor-item { flex: 1; display: flex; flex-direction: column; :deep(.el-form-item__content) { flex: 1; display: flex; flex-direction: column; } } .sql-editor-container { flex: 1; display: flex; flex-direction: column; border: 1px solid var(--el-border-color-light); border-radius: 4px; overflow: hidden; } .sql-tips { padding: 8px 12px; background: var(--el-color-info-light-9); border-top: 1px solid var(--el-border-color-light); .el-tag { width: 100%; justify-content: flex-start; .el-icon { margin-right: 6px; } } } .form-actions { margin-top: 24px; padding-top: 16px; display: flex; justify-content: flex-end; gap: 16px; border-top: 1px dashed var(--el-border-color); } .refresh-btn { :deep(.el-icon) { margin-right: 6px; } } @media (max-width: 992px) { .layout-container { flex-direction: column; } .config-aside { width: 100% !important; border-right: none; border-bottom: 1px solid var(--el-border-color-light); } } </style> ``` ### 代码2:src\views\handle\dataset\add\AddView.vue ```vue <template> <div class="sql-dataset-container"> <el-container class="layout-container"> <!-- 左侧:数据集名称和SQL查询 --> <el-aside class="config-aside"> <div class="config-section"> <div class="section-header"> <h2><el-icon><setting /></el-icon> SQL数据集配置</h2> </div> <el-form :model="form" :rules="rules" label-position="top" class="config-form" ref="formRef" > <!-- 数据集名称 --> <el-form-item v-if="props.id === ''" label="数据集名称" prop="name" class="form-item-card inline-form-item"> <el-input v-model="form.name" placeholder="例如: 用户行为分析数据集" size="large" :prefix-icon="Document" /> </el-form-item> <!-- SQL编辑器 --> <el-form-item label="SQL查询" prop="sql" class="form-item-card sql-editor-item"> <div class="sql-editor-container"> <VAceEditor v-model:value="form.sql" lang="sql" theme="github" style="height: 100%; width: 100%" :options="{ enableBasicAutocompletion: true, enableLiveAutocompletion: true, highlightActiveLine: true, showLineNumbers: true, tabSize: 2, }" /> <div class="sql-tips"> <el-tag type="info" size="small"> <el-icon><info-filled /></el-icon> 提示: 请确保SQL语法正确且符合数据源规范 </el-tag> </div> </div> </el-form-item> </el-form> </div> </el-aside> <!-- 右侧:数据预览 --> <el-main class="preview-main"> <div class="preview-section"> <div class="section-header"> <div class="header-left"> <h2><el-icon><data-line /></el-icon> 数据预览</h2> </div> <div class="header-right"> <el-button type="warning" plain size="small" @click="emit('cancel')" :icon="Close">取消</el-button> <el-button type="success" plain size="small" :disabled="!form.sql" @click="fetchPreviewData" :icon="Refresh">执行SQL预览</el-button> <el-button type="primary" plain size="small" @click="handleSave" :icon="Check">保存</el-button> </div> </div> <div class="preview-content"> <zr-table :tableModule="tableModule" /> </div> </div> </el-main> </el-container> </div> </template> <script setup> import {ref, reactive, onMounted, getCurrentInstance, watch} from 'vue' import { VAceEditor } from 'vue3-ace-editor' import '@/components/CodeEdit/ace-config.js' import { Setting, DataLine, Document, Refresh, Check, Close, InfoFilled, Edit, Download } from '@element-plus/icons-vue' import ZrTable from "@/components/ZrTable/index.vue"; import {buildFilterSos} from "@/components/ZrTable/table.js"; import { handleDataSetCreate, handleDataSetGet, handleDataSetPreviewData, handleDataSetReconstruct } from "@/api/handle/dataset.js"; const { proxy } = getCurrentInstance() const props = defineProps({ sceneId:{ type: String, default: '', }, id:{ type: String, default: '', } }) const emit = defineEmits(['saved', 'cancel']) const form = ref({ id:props.id, name: '', sceneId:props.sceneId, type:'view', sql: '', info:'', }) //数据预览 const columnsData= ref([]) const queryData = ref([]) const state = reactive({ columns: columnsData, // 表格配置 query: queryData, // 查询条件配置 queryForm: {}, // 查询form表单 loading: false, // 加载状态 dataList: [], // 列表数据 pages:false, }) const { loading, dataList, columns, pages, query, queryForm } = toRefs(state) const formRef = ref(null) // 预览数据 // 传给子组件的 const tableModule = ref({ callback: fetchPreviewData, // 回调,子组件中可以看到很多调用callback的,这里对应的是获取列表数据的方法 // 以下说了,上面都给解释了 queryForm, columns, dataList, loading, pages, query, }) const rules = reactive({ name: [ { required: true, message: '请输入数据集名称', trigger: 'blur' }, { max: 50, message: '名称长度能超过50个字符', trigger: 'blur' } ], sql: [ { required: true, message: '请输入SQL查询语句', trigger: 'blur' }, { validator: (rule, value, callback) => { if (!value || !value.trim()) { callback(new Error('SQL能为空')) } else if (!value.toLowerCase().includes('select')) { callback(new Error('SQL必须是SELECT查询语句')) } else { callback() } }, trigger: 'blur' } ] }) async function fetchPreviewData() { state.loading = true const valid = await formRef.value.validate() if (!valid) { proxy.$modal.msgError("校验错误,请修改好再次执行") return } // 掉自己的接口,切勿复制粘贴 handleDataSetPreviewData({ sql:form.value.sql }).then((res) => { if (res.success) { columnsData.value = []; // 重置列数据 if(res.data.pageData.length>0){ for (let key in res.data.pageData[0]) { columnsData.value.push({prop: key, label:key, align: 'center',show:1}) } } state.dataList = res.data.pageData proxy.$modal.msgSuccess('SQL执行成功') } else { proxy.$modal.msgError(res.message) } }) state.loading = false } const handleSave = async () => { const valid = await formRef.value.validate() if (!valid) return form.value.info=JSON.stringify({ sql:form.value.sql }) const method=form.value.id===""?handleDataSetCreate:handleDataSetReconstruct method(form.value).then((res) => { if (res.success) { emit('saved',{id: res.data }) } else { proxy.$modal.msgError(res.message) } }) } watch(() => props.id, (newid, oldid) => { if (newid !== oldid && newid!=="") { // 引用变化时触发 handleDataSetGet(newid).then((res) => { if(res.success){ form.value.sql=JSON.parse(res.data.info)?.sql; } }) } }, { immediate: true } // 注意:要用 deep: true ); </script> <style scoped lang="scss"> .sql-dataset-container { height: calc(90vh - 60px); padding: 16px; background-color: #f5f7fa; } .layout-container { height: 100%; background: #fff; border-radius: 8px; box-shadow: 0 1px 6px rgba(0, 0, 0, 0.05); overflow: hidden; display: flex; /* 使用flex横向布局 */ flex-direction: row; /* 横向排列 */ } .config-aside { background: #f9fafc; border-right: 1px solid var(--el-border-color-light); /* 右侧边框分隔 */ padding: 18px; display: flex; flex-direction: column; width: 50%; /* 左侧占50%宽度 */ min-width: 300px; /* 最小宽度限制 */ } .preview-main { padding: 18px; background: #fff; display: flex; flex-direction: column; flex: 1; /* 右侧占剩余宽度 */ min-width: 300px; /* 最小宽度限制 */ } .section-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; margin-bottom: 16px; gap: 12px; h2 { margin: 0; color: var(--el-text-color-primary); font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 6px; white-space: nowrap; } } .preview-content { flex: 1; border: 1px solid var(--el-border-color-light); border-radius: 6px; overflow: hidden; display: flex; flex-direction: column; min-height: 250px; } .config-form { flex: 1; display: flex; flex-direction: column; :deep(.el-form-item) { margin-bottom: 16px; } } .form-item-card { background: #fff; padding: 12px; border-radius: 6px; border-left: 2px solid var(--el-color-primary); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); } .sql-editor-container { flex: 1; display: flex; flex-direction: column; border: 1px solid var(--el-border-color-light); border-radius: 4px; overflow: hidden; min-height: 220px; max-height: 400px; } .sql-tips { padding: 6px 10px; background: var(--el-color-info-light-9); border-top: 1px solid var(--el-border-color-light); .el-tag { width: 100%; justify-content: flex-start; font-size: 11px; padding: 4px 8px; } } /* 响应式调整 */ @media (max-width: 992px) { .layout-container { flex-direction: column; /* 小屏幕下改为纵向布局 */ } .config-aside, .preview-main { width: 100%; /* 占满宽度 */ min-width: auto; /* 取消最小宽度限制 */ } .config-aside { border-right: none; border-bottom: 1px solid var(--el-border-color-light); /* 底部边框分隔 */ max-height: 50%; /* 限制最大高度 */ } .sql-dataset-container { padding: 12px; height: auto; min-height: 100vh; } .section-header { flex-direction: column; align-items: stretch; .header-left, .header-right { width: 100%; } .header-right { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; } } .sql-editor-container { max-height: 250px; min-height: 180px; } } @media (max-width: 768px) { .preview-content { min-height: 200px; } .section-header { gap: 8px; h2 { font-size: 15px; } } .header-right { grid-template-columns: repeat(2, 1fr) !important; .el-button { width: 100%; margin: 0 !important; } .el-button:last-child { grid-column: span 2; } } } @media (max-width: 480px) { .sql-dataset-container { padding: 8px; } .config-aside, .preview-main { padding: 12px; } .sql-editor-container { min-height: 150px; max-height: 200px; } .form-item-card { padding: 8px; } .el-input, .el-button { font-size: 13px !important; } } </style> ```
07-24
``` <div class="chat-right-content w100 h100"> <div class="chat-right-content-list w100" @scroll="handleScroll"> </div> <!-- 底部输入框 --> <div class="chat-input w100"> <div class="chat-input-warp w100" > <!-- <textarea @submit.prevent="getQuestion(1)" class="chat-input-box" :placeholder="T.placeholder" v-html="mdRender(contentVal)" v-model="contentVal" :disabled="isProcessing"></textarea> --> <div @keydown.enter.prevent="handleEnter" class="chat-input-box" contenteditable="true" ref="contentEditable" :placeholder="contentVal ? '' : placeholder" enterkeyhint="send" v-contenteditable="contentVal" @input="onInput" :disabled="isProcessing" tabindex="0" autocomplete="off" spellcheck="false" ></div> <div class="chat-input-btn-warp flex justify-content-between align-items-center"> <!-- 提交 --> <tooltip-components :key="tipKey" :content="isProcessing ? T.talkPadding : (contentVal? '' : T.inputTip)"> <el-button :disabled="isProcessing || !contentVal" size="small" type="primary" :icon="isProcessing ? 'el-icon-loading' : 'el-icon-top'" circle style="font-size: 15px; margin-left: 5px; font-weight: 900" @click="getQuestion(1)" ></el-button> </tooltip-components> </div> </div> </div> </div>2```代码其他逻辑都要管 只加一个chat-input-box输入框输入内容过多 chat-input自动加高 是向上1加高度 设定一个最高 超过最高在加高 内容过多加高时需要动态减掉chat-right-content-list对应的高度 contentVal如果没值了 chat-input和chat-input-box,chat-right-content-list就恢复原来高度
03-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值