Browse Source

查询模板生成,筛选、维度、度量选择。

周壕 1 year ago
parent
commit
bacf26e270

+ 14 - 1
pom.xml

@@ -145,7 +145,7 @@
             <artifactId>aspose-words</artifactId>
             <version>15.8.0</version>
             <scope>system</scope>
-            <systemPath>${project.basedir}\lib\aspose-words-15.8.0-jdk16.jar</systemPath>
+            <systemPath>${project.basedir}/lib/aspose-words-15.8.0-jdk16.jar</systemPath>
         </dependency>
     </dependencies>
 
@@ -191,6 +191,12 @@
             </plugin>
         </plugins>
         <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
             <resource>
                 <filtering>true</filtering>
                 <directory>src/main/resources</directory>
@@ -198,6 +204,13 @@
                     <exclude>static/**</exclude>
                 </excludes>
             </resource>
+            <resource>
+                <directory>src/main/resources</directory>
+                <targetPath>BOOT-INF/</targetPath>
+                <includes>
+                    <include>/lib/*.jar</include>
+                </includes>
+            </resource>
         </resources>
     </build>
 

+ 2 - 2
target/classes/application.yml

@@ -1,5 +1,5 @@
 server:
-  port: 8088
+  port: 8077
   tomcat:
     max-connections: 100000
     max-http-form-post-size: 102400000
@@ -15,7 +15,7 @@ spring:
       filters: stat
       driver-class-name: com.mysql.cj.jdbc.Driver
       #基本属性
-      url: jdbc:mysql://192.168.0.68:3306/smartSearchDB?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true
+      url: jdbc:mysql://office.bowintek.com:3306/smartSearchDB?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true
       #url: jdbc:mysql://office.bowintek.com:3306/practicedb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&allowPublicKeyRetrieval=true
       username: root
       password: bowin123

+ 167 - 0
vue/src/components/basic/query/dimension-expired.vue

@@ -0,0 +1,167 @@
+<template>
+  <div class="query-area"
+       @dragover.prevent=""
+       @dragenter.prevent="handleDragOver($event)"
+       @drop="handleDrop($event)"
+       @dragleave="handleDragleave($event)"
+  >
+    <template v-for="(it, index) in areas.tagList">
+      <div :class="(areas.index == index?'query-area-item query-area-item-active':'query-area-item')"
+           draggable="true"
+           @dragstart="childDragstart($event, index)"
+           @dragenter.prevent="childDragEnter($event, index)"
+           @dragleave="childDragleave($event, index)">
+        <div class="query-area-item-title">{{it.title}}</div>
+        <div v-if="(queryType=='measure')" class="query-area-item-select"></div>
+        <div class="query-area-item-close" @click="childDelete(index)"><CloseOutlined style="color: white;"/></div>
+      </div>
+    </template>
+  </div>
+</template>
+<script lang="ts" setup>
+  import {reactive, ref, computed, defineComponent} from 'vue';
+
+  const props = defineProps(['queryType']);
+  const queryType = computed(() => {
+    return props.queryType ? props.queryType : 'dimen'
+  });
+
+  let areas = reactive({
+    tagList : [{title:'张三封', columnName:'textss'}],
+    counter : 0,
+    index : -1
+  });
+
+  const handleDragOver = function (ev) {
+    ev.preventDefault();
+    areas.counter++;
+    const data = window["dragData"];
+    const tag = findTag(data.columnName);
+    console.log("handleDragOver", data, tag);
+
+    if(tag!=null) return;
+    areas.index = areas.tagList.length;
+
+    areas.tagList.push({
+      columnName : data.columnName,
+      title: data.title,
+      isDrag : 1
+    });
+  };
+  const handleDrop = (ev) => {
+    ev.preventDefault();
+    const data = window["dragData"];
+    const tag = findTag(data.columnName);
+
+    console.log("handleDrop", data, tag);
+    if(tag==null) return;
+    areas.tagList[tag.index].isDarg = 0;
+    areas.index = -1;
+    areas.counter = 0;
+  };
+
+  const handleDragleave = (ev) => {
+    areas.counter--;
+    const data = window["dragData"];
+    const tag = findTag(data.columnName);
+    console.log("handleDragleave", data, tag, areas.counter);
+
+    if(tag==null) return;
+    if(areas.counter==0) {
+      areas.tagList.splice(tag.index,1);
+      areas.index = -1;
+    }
+  };
+
+  const childDragEnter = function (ev, index) {
+    ev.preventDefault();
+    areas.counter++;
+    //重新排序
+    if(index == areas.index || areas.index==-1) return;
+
+    const data = window["dragData"];
+    areas.tagList.splice(areas.index,1)
+    areas.index = index;
+
+    areas.tagList.splice(index, 0, {
+      columnName : data.columnName,
+      title: data.title,
+      isDrag : 1
+    });
+    console.log("childDragEnter","位置切换");
+  };
+  const childDragleave = function (ev) {
+    ev.preventDefault();
+    areas.counter--;
+  };
+  const childDragstart = function (ev, index) {
+    window["dragData"] = JSON.parse(JSON.stringify(areas.tagList[index]));
+    areas.index = -1;
+    setTimeout(function (){
+      areas.tagList.splice(index,1);
+      console.log("childDragstart", window["dragData"], index, areas.tagList);
+    },20);
+  };
+  const childDelete = function (index){
+    areas.index = -1;
+    areas.tagList.splice(index,1);
+  }
+
+  const findTag = (key) => {
+    console.log("findTag",areas.tagList);
+    for (let i = 0; i < areas.tagList.length; i++) {
+      if (areas.tagList[i] && areas.tagList[i].columnName == key)
+        return {obj: areas.tagList[i], index: i}
+    }
+    return null;
+  }
+</script>
+
+<style lang="less" scoped>
+  .query-area{
+    background-color: #f9f9f9;
+    min-height: 41px;
+    padding-left: 5px;
+    padding-right: 5px;
+  }
+  .query-area-item{
+    display: inline-block;
+    padding: 0px 5px 0px 5px;
+    background-color: #8DC6F9;
+    border-radius: 3px;
+    margin-right: 10px;
+    margin-top: 5px;
+  }
+  .query-area-item-active{
+    background-color: orangered;
+  }
+  .query-area-item-test{
+    pointer-events: none;
+  }
+  .query-area-item-title{
+    display: inline-block;
+    line-height: 32px;
+    height: 32px;
+    vertical-align: middle;
+  }
+  .query-area-item-close{
+    display: inline-block;
+    width: 18px;
+    height: 18px;
+    padding-top: 5px;
+    margin-left: 3px;
+    cursor: pointer;
+    height: 32px;
+    vertical-align: middle;
+  }
+  .query-area-item-select{
+    display: inline-block;
+    line-height: 32px;
+    min-width: 60px;
+    height: 32px;
+    vertical-align: middle;
+    margin-left: 5px;
+    padding-left: 5px;
+    border-left: 1px solid #ffffff;
+  }
+</style>

+ 46 - 0
vue/src/components/basic/query/drag-base.less

@@ -0,0 +1,46 @@
+.query-area{
+  background-color: #f9f9f9;
+  min-height: 41px;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+.query-area-item{
+  display: inline-block;
+  padding: 1px 5px 1px 5px;
+  background-color: #8DC6F9;
+  border-radius: 3px;
+  margin-right: 10px;
+  margin-top: 5px;
+}
+.query-area-item-active{
+  background-color: orangered;
+}
+.query-area-item-test{
+  pointer-events: none;
+}
+.query-area-item-title{
+  display: inline-block;
+  line-height: 32px;
+  height: 32px;
+  vertical-align: middle;
+}
+.query-area-item-close{
+  display: inline-block;
+  width: 18px;
+  height: 18px;
+  padding-top: 5px;
+  margin-left: 3px;
+  cursor: pointer;
+  height: 32px;
+  vertical-align: middle;
+}
+.query-area-item-select{
+  display: inline-block;
+  line-height: 32px;
+  min-width: 90px;
+  height: 32px;
+  vertical-align: middle;
+  margin-left: 5px;
+  padding-left: 5px;
+  border-left: 1px solid #ffffff;
+}

+ 190 - 0
vue/src/components/basic/query/drag-base.vue

@@ -0,0 +1,190 @@
+<template>
+  <div class="query-area"
+       @dragover.prevent=""
+       @dragenter.prevent="handleDragOver($event)"
+       @drop="handleDrop($event)"
+       @dragleave="handleDragleave($event)"
+  >
+    <template v-for="(it, index) in tagList">
+      <div :class="(this.index == index?'query-area-item query-area-item-active':'query-area-item')"
+           draggable="true"
+           @dragstart="childDragstart($event, index)"
+           @dragenter.prevent="childDragEnter($event, index)"
+           @dragleave="childDragleave($event, index)">
+        <div class="query-area-item-title" @click="handleDisplay(index)">{{it.fieldAlias?it.fieldAlias : it.fieldName}}</div>
+        <div v-if="(queryType=='measure')" class="query-area-item-select">
+          <a-select v-model:value="tagList[index].operation" :options="aggregates"></a-select>
+        </div>
+        <div class="query-area-item-close" @click="childDelete(index)"><CloseOutlined style="color: white;"/></div>
+      </div>
+    </template>
+  </div>
+
+  <a-modal v-model:visible="modelName.open" title="修改显示名称" @ok="handleDisplayOk">
+    <div style="line-height: 30px;">属性名称:{{modelName.fieldName}}</div>
+    <div>
+      <a-input v-model:value="modelName.fieldAlias"
+               :placeholder="(modelName.fieldName+'...')" />
+    </div>
+  </a-modal>
+</template>
+<script lang="ts">
+  import {defineComponent} from "vue";
+  export  default defineComponent ({
+    props:{
+      queryType : String,
+      keyName : String
+    },
+    emits: ['change'],
+    name : 'DragBase',
+    data() {
+      return{
+        tagList : [] as any,
+        counter : 0,
+        index : -1,
+        aggregates : [],
+        modelName : {
+          open : false,
+          fieldName: '',
+          fieldAlias : '',
+          index : -1
+        }
+      }
+    },
+    setup(props, context){
+      console.log("base setup" , props);
+
+      const dataChange = (obj) => {
+        context.emit('change', obj)
+      }
+
+      return{
+        queryType: props.queryType ? props.queryType : 'dimen',
+        keyName : props.keyName == null || props.keyName == undefined ? 'tempFeildId' : props.keyName,
+        dataChange
+      }
+    },
+    mounted(){
+      console.log("mounted" , "");
+    },
+    methods:{
+      getObject:function (data){
+        return {
+          tempFeildId : data.tempFeildId,
+          fieldId: data.fieldId,
+          fieldName: data.fieldName,
+          isDrag: 1,
+          fieldAlias: data.fieldAlias ? data.fieldAlias : null,
+          operation : data.operation? data.operation : 'sum'
+        };
+      },
+      handleDragOver : function (ev) {
+        ev.preventDefault();
+        this.counter++;
+        const data = window["dragData"];
+        console.log("keyName", this.keyName, data[this.keyName])
+        const tag = this.findTag(data[this.keyName]);
+
+        if(tag!=null) return;
+        this.index = this.tagList.length;
+
+        this.tagList.push(this.getObject(data));
+        console.log("handleDragOver", data, tag, this.tagList);
+      },
+      handleDrop : function (ev) {
+        ev.preventDefault();
+        const data = window["dragData"];
+        const tag = this.findTag(data[this.keyName]);
+
+        console.log("handleDrop", data, tag);
+        if(tag==null) return;
+        this.tagList[tag.index]['isDrag'] = 0;
+        this.index = -1;
+        this.counter = 0;
+        this.dataChange(this.getTagList());
+      },
+      handleDragleave : function(ev) {
+        console.log(ev);
+        this.counter--;
+        const data = window["dragData"];
+        const tag = this.findTag(data[this.keyName]);
+        console.log("handleDragleave", data, tag, this.counter);
+
+        if(tag==null) return;
+        if(this.counter==0) {
+          this.tagList.splice(tag.index,1);
+          this.index = -1;
+          this.dataChange(this.getTagList());
+        }
+      },
+      childDragEnter : function (ev, index) {
+        ev.preventDefault();
+        this.counter++;
+        //重新排序
+        if(index == this.index || this.index==-1) return;
+
+        const data = window["dragData"];
+        this.tagList.splice(this.index,1)
+        this.index = index;
+
+        this.tagList.splice(index, 0, this.getObject(data));
+        console.log("childDragEnter","位置切换");
+
+        this.dataChange(this.getTagList());
+      },
+      childDragleave : function (ev) {
+        ev.preventDefault();
+        this.counter--;
+      },
+      childDragstart : function (ev, index) {
+        console.log(ev);
+        window["dragData"] = JSON.parse(JSON.stringify(this.tagList[index]));
+        this.index = -1;
+        setTimeout(()=>{
+          this.tagList.splice(index,1);
+          console.log("childDragstart", window["dragData"], index, this.tagList);
+          this.dataChange(this.getTagList());
+        },20);
+      },
+      childDelete : function (index){
+        this.index = -1;
+        this.tagList.splice(index,1);
+        this.dataChange(this.getTagList());
+      },
+      findTag : function (key) {
+        for (let i = 0; i < this.tagList.length; i++) {
+          if (this.tagList[i][this.keyName] == key) {
+            console.log("findTag",this.tagList, key, this.tagList[i][this.keyName]);
+            return {obj: this.tagList[i], index: i}
+          }
+        }
+        console.log("findTag",this.tagList, key, "null");
+        return null;
+      },
+      handleDisplay(index){
+        this.modelName.index = index;
+        this.modelName.open = true;
+        this.modelName.fieldName = this.tagList[index]["fieldName"];
+        this.modelName.fieldAlias = this.tagList[index]["fieldAlias"]?
+          this.tagList[index]["fieldAlias"]:this.tagList[index]["fieldName"];
+      },
+      handleDisplayOk(){
+        if(this.modelName.fieldAlias!=null && this.modelName.fieldAlias!=undefined && this.modelName.fieldAlias.length>0) {
+          this.modelName.open = false;
+          this.tagList[this.modelName.index]["fieldAlias"] = this.modelName.fieldAlias;
+          this.modelName.index = -1;
+        }
+      },
+      getTagList(){
+        this.tagList.forEach((row, index)=>{
+          row.disOrder = index;
+        });
+        return this.tagList;
+      }
+    }
+  })
+</script>
+
+<style lang="less" scoped>
+  @import 'drag-base.less';
+</style>

+ 39 - 0
vue/src/components/basic/query/drag-measure.vue

@@ -0,0 +1,39 @@
+<script lang="ts">
+  import DragBase from './drag-base.vue'
+  import type {SelectProps} from 'ant-design-vue';
+
+  export default {
+    name : 'DragMeasure',
+    extends : DragBase,
+    data(){
+      return{
+        aggregates : [
+          { value: 'sum', label: '求和'},
+          { value: 'avg', label: '平均'},
+          { value: 'max', label: '最大值'},
+          { value: 'min', label: '最小值'},
+          { value: 'count', label: '非NULL行数'}
+        ] as SelectProps['options']
+      }
+    },
+    setup(props, context){
+      console.log("measure setup" , props);
+      const dataChange = (obj) => {
+        console.log("dataChange==",obj);
+        context.emit('change', obj)
+      }
+
+      return{
+        queryType: props.queryType ? props.queryType : 'dimen',
+        keyName : props.keyName == null || props.keyName == undefined ? 'tempFeildId' : props.keyName,
+        dataChange
+      }
+    },
+    methods:{
+
+    }
+  }
+</script>
+<style lang="less" scoped>
+  @import 'drag-base.less';
+</style>

+ 225 - 0
vue/src/components/basic/query/drag-where.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="query-area"
+       @dragover.prevent=""
+       @dragenter.prevent="handleDragOver($event)"
+       @drop="handleDrop($event)"
+       @dragleave="handleDragleave($event)"
+  >
+    <template v-for="(it, index) in tagList">
+      <div class="query-area-item"
+           :class="((this.index == index?'query-area-item-active':'') + (convertDataType(tagList[index].dataType)==2?' query-area-item-time':''))"
+           draggable="true"
+           @dragstart="childDragstart($event, index)"
+           @dragenter.prevent="childDragEnter($event, index)"
+           @dragleave="childDragleave($event, index)">
+        <div class="query-area-header">
+          <div class="query-area-item-title" @click="handleDisplay(index)">{{it.fieldAlias?it.fieldAlias : it.fieldName}}</div>
+          <div class="query-area-item-close" @click="childDelete(index)"><CloseOutlined style="color: white;"/></div>
+        </div>
+        <template v-for="(op, idx) in tagList[index].values">
+          <div v-if="convertDataType(tagList[index].dataType)==0" class="query-area-body">
+            <div class="query-area-body-oper">
+              <a-select v-model:value="tagList[index].values[idx].operation" :options="operTexts"></a-select>
+            </div>
+            <div class="query-area-body-input">
+              <a-input v-model:value="tagList[index].values[idx].val" placeholder="值..." />
+            </div>
+            <div v-if="false" class="query-area-body-edit">
+              <PlusSquareOutlined v-if="(idx==0)" @click="operationInsert(index, idx)"/>
+              <MinusSquareOutlined  v-if="(idx>0)" @click="operationDelete(index, idx)"/>
+            </div>
+          </div>
+
+          <div v-if="convertDataType(tagList[index].dataType)==1" class="query-area-body">
+            <div class="query-area-body-oper">
+              <a-select v-model:value="tagList[index].values[idx].operation" :options="operNumbers"></a-select>
+            </div>
+            <div class="query-area-body-input">
+              <a-input v-model:value="tagList[index].values[idx].val" placeholder="值..." />
+            </div>
+            <div v-if="tagList[index].values[idx].operation=='limit'" class="query-area-body-text">
+              到
+            </div>
+            <div v-if="tagList[index].values[idx].operation=='limit'" class="query-area-body-input">
+              <a-input v-model:value="tagList[index].values[idx].val2" placeholder="值..." />
+            </div>
+            <div v-if="false" class="query-area-body-edit">
+              <PlusSquareOutlined v-if="(idx==0)" @click="operationInsert(index, idx)"/>
+              <MinusSquareOutlined  v-if="(idx>0)" @click="operationDelete(index, idx)"/>
+            </div>
+          </div>
+
+          <div v-if="convertDataType(tagList[index].dataType)==2" class="query-area-body">
+            <div class="query-area-body-oper">
+              <a-select v-model:value="tagList[index].values[idx].operation" :options="operTimes"></a-select>
+            </div>
+            <div class="query-area-body-input">
+              <a-range-picker
+                v-if="(tagList[index].values[idx].operation=='datetime')"
+                v-model:value="tagList[index].values[idx].val"
+                :show-time="{ format: 'HH:mm' }"
+                format="YYYY-MM-DD HH:mm"
+                :placeholder="['Start Time', 'End Time']"
+                @change="onRangeChange"
+                @ok="onRangeOk"
+              />
+              <a-range-picker
+                v-if="(tagList[index].values[idx].operation=='date')"
+                v-model:value="tagList[index].values[idx].val"/>
+            </div>
+            <div v-if="false" class="query-area-body-edit">
+              <PlusSquareOutlined v-if="(idx==0)" @click="operationInsert(index, idx)"/>
+              <MinusSquareOutlined  v-if="(idx>0)" @click="operationDelete(index, idx)"/>
+            </div>
+          </div>
+        </template>
+      </div>
+    </template>
+  </div>
+
+  <a-modal v-model:visible="modelName.open" title="修改显示名称" @ok="handleDisplayOk">
+    <div style="line-height: 30px;">属性名称:{{modelName.fieldName}}</div>
+    <div>
+      <a-input v-model:value="modelName.fieldAlias"
+               :placeholder="(modelName.fieldName+'...')" />
+    </div>
+  </a-modal>
+</template>
+<script lang="ts">
+  import {defineComponent} from "vue";
+  import DragBase from './drag-base.vue'
+  import type {SelectProps} from 'ant-design-vue';
+  import type { Dayjs } from 'dayjs';
+
+  export default defineComponent({
+    name : 'DragWhere',
+    extends : DragBase,
+    data(){
+      return{
+        operTexts : [
+          { value: '=', label: '等于'},
+          { value: 'like', label: '包含'},
+          { value: 'null', label: '为空'},
+          { value: 'not null', label: '不为空'}
+        ] as SelectProps['options'],
+        operTimes : [
+          { value: 'date', label: '日期'},
+          { value: 'datetime', label: '日期时间'}
+        ] as SelectProps['options'],
+        operNumbers : [
+          { value: '>', label: '大于'},
+          { value: '>=', label: '大于等于'},
+          { value: '=', label: '等于'},
+          { value: '<', label: '小于'},
+          { value: '<=', label: '小于等于'},
+          { value: 'limit', label: '区间'}
+        ] as SelectProps['options'],
+      }
+    },
+    setup(props, context){
+      console.log("measure setup" , props);
+      const dataChange = (obj) => {
+        context.emit('change', obj)
+      }
+
+      return{
+        queryType: props.queryType ? props.queryType : 'dimen',
+        keyName : props.keyName == null || props.keyName == undefined ? 'tempFeildId' : props.keyName,
+        dataChange
+      }
+    },
+    methods:{
+      getObject:function (data){
+        data.dataType = 'int';
+        return {
+          tempFeildId : data.tempFeildId,
+          fieldId : data.fieldId,
+          fieldName: data.fieldName,
+          isDrag : 1,
+          dataType : data.dataType,
+          fieldAlias: data.fieldAlias ? data.fieldAlias : null,
+          values : data.values ? data.values :
+            [{ operation : (this.convertDataType(data.dataType)==2?'date':'='), val :'', val2 : '' }]
+        };
+      },
+      operationInsert(index){
+        this.tagList[index].values.push({ operation : '=', val :'' });
+      },
+      operationDelete(index, idx){
+        this.tagList[index].values.splice(idx, 1);
+      },
+      convertDataType(typeStr){
+        if(["char","varchar","text"].indexOf(typeStr)>=0) return 0;
+        if(["int","bigint","decimal"].indexOf(typeStr)>=0) return 1;
+        if(["dtime"].indexOf(typeStr)>=0) return 2;
+      },
+      onRangeChange(value: [Dayjs, Dayjs], dateString: [string, string]) {
+        console.log('Selected Time: ', value);
+        console.log('Formatted Selected Time: ', dateString);
+      },
+      getTagList(){
+        this.tagList.forEach((row, index)=>{
+          row.disOrder = index;
+          row.operation = row.values[0].operation;
+          if(this.convertDataType(row.dataType)==2){
+            let formatString = "YYYY-MM-DD";
+            if(row.values[0].operation=="datetime") formatString="YYYY-MM-DD HH:mm:ss";
+
+            if(row.values[0].val && row.values[0].val.length>0 && row.values[0].val[0]){
+              row.value1 = row.values[0].val[0].toDate().getTime();
+              row.valueStr1 = row.values[0].val[0].format(formatString);
+            }
+            if(row.values[0].val && row.values[0].val.length>1 && row.values[0].val[1]) {
+              row.value2 = row.values[0].val[1].toDate().getTime();
+              row.valueStr2 = row.values[0].val[1].format(formatString);
+            }
+          }
+          else {
+            row.value1 = row.values[0].val;
+            row.value2 = row.values[0].val2;
+          }
+        });
+        return this.tagList;
+      }
+    }
+  })
+</script>
+<style lang="less" scoped>
+  @import 'drag-base.less';
+  .query-area-header{
+    display: flex;
+    flex-direction: row;
+  }
+  .query-area-header .query-area-item-title{
+    flex-grow: 1;
+  }
+  .query-area-body{
+    display: flex;
+    flex-direction: row;
+    margin-bottom: 5px;
+  }
+  .query-area-body-oper{
+    width: 100px;
+  }
+  .query-area-body-input{
+    flex-grow: 1;
+  }
+  .query-area-item{
+    width: 280px !important;
+  }
+  .query-area-item-time{
+    width: 380px !important;
+  }
+  .query-area-body-edit{
+    font-size: 18px;
+    padding-left: 5px;
+    cursor: pointer;
+  }
+  .query-area-body-text{
+    height: 30px;
+    line-height: 30px;
+    padding-left: 5px;
+    padding-right: 5px;
+    margin-top: 1px;
+  }
+</style>

+ 4 - 3
vue/src/layout/menu/menu-item.vue

@@ -18,9 +18,10 @@
   <!-- 菜单 -->
   <template v-else>
     <Menu.Item :key="props.menuInfo?.name" :menuname="props.menuInfo?.meta?.title">
-<!--      <icon-font :type="props.menuInfo?.meta?.icon" />-->
-      <component :is="$antIcons[props.menuInfo.meta?.icon]" />
-      <TitleI18n :title="props.menuInfo?.meta?.title" />
+      <div v-dragtheme="{themeType:'dimension', fieldName:props.menuInfo?.meta?.title, fieldId:props.menuInfo?.meta?.id}">
+        <component :is="$antIcons[props.menuInfo.meta?.icon]"/>
+        <TitleI18n :title="props.menuInfo?.meta?.title"/>
+      </div>
     </Menu.Item>
   </template>
 </template>

+ 12 - 0
vue/src/main.ts

@@ -38,6 +38,18 @@ async function setupApp() {
   await setupRouter(app);
 
   app.mount('#app');
+
+  //增加拖放指令,用于维度、度量拖放
+  app.directive('dragtheme', (el, data) =>{
+    el.draggable = true;
+    el.ondragstart = function (event){
+      event.dataTransfer.setData("dragData", data.value);
+      data.value.tempFeildId = new Date().getTime()+'';
+
+      window["dragData"] = data.value;
+      console.log("binding",data.value, event, event.dataTransfer.getData("dragData"));
+    }
+  });
 }
 
 setupPlugins();

+ 1 - 0
vue/src/router/asyncModules/sale.ts

@@ -2,4 +2,5 @@ export default {
   'views/positionlist': () => import( '@/views/position/index.vue'),
   'views/positionedit': () => import( '@/views/position/edit.vue'),
   'views/positiondetail': () => import( '@/views/position/detail.vue'),
+  'views/queryindex': () => import( '@/views/query/index.vue'),
 }

+ 207 - 0
vue/src/views/query/index.vue

@@ -0,0 +1,207 @@
+<template>
+  <div class="query-index">
+    <div class="query-index-tree"></div>
+    <div class="query-index-content">
+      <div class="query-index-form">
+        <a-form ref="formRef" name="fromQuery"
+                class="ant-advanced-search-form"
+                :label-col="labelCol"
+                :model="formState">
+          <a-row :gutter="24">
+            <a-col :span="18">
+              <a-form-item name="tempName"
+                           :rules="[{ required: true, message: '请输入模板名称!' }]">
+                <template #label>
+                  <span>模板名称</span>
+                </template>
+                <a-input v-model:value="formState.tempName" placeholder="请输入模板名称,用以保存..." />
+              </a-form-item>
+            </a-col>
+            <a-col :span="6" style="text-align: left">
+              <a-button type="primary" html-type="submit" @click="onFinish">查询预览</a-button>
+              <a-button html-type="submit" @click="onFinish" style="margin-left: 10px;">保存模板</a-button>
+            </a-col>
+
+            <a-col :span="24">
+              <a-form-item>
+                <template #label>
+                  <SearchOutlined style="color: #8DC6F9;"/>
+                  <span>筛选</span>
+                </template>
+                <DragWhere ref="where" @change="onChangeWhere" query-type="where"></DragWhere>
+              </a-form-item>
+            </a-col>
+
+            <a-col :span="24">
+              <a-form-item>
+                <template #label>
+                  <ColumnHeightOutlined style="color: #8DC6F9;"/>
+                  <span>维度</span>
+                </template>
+                <DragBase ref="base" @change="onChangeBase"></DragBase>
+              </a-form-item>
+            </a-col>
+
+            <a-col :span="24">
+              <a-form-item>
+                <template #label>
+                  <ColumnWidthOutlined style="color: #8DC6F9;"/>
+                  <span>度量</span>
+                </template>
+                <DragMeasure ref="measure" @change="onChangeMeasure" query-type="measure"></DragMeasure>
+              </a-form-item>
+            </a-col>
+          </a-row>
+        </a-form>
+      </div>
+      <div class="query-index-table">
+        <a-table :columns="columns" :data-source="dataList" :scroll="{ x:'100%', y: 500 }"
+                 bordered>
+        </a-table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import {ref, defineComponent} from 'vue';
+import type {FormInstance} from 'ant-design-vue';
+import {useRoute} from 'vue-router';
+import DragWhere from '@/components/basic/query/drag-where.vue'
+import DragBase from '@/components/basic/query/drag-base.vue'
+import DragMeasure from '@/components/basic/query/drag-measure.vue'
+import {save} from "@/api/common";
+import type {TableColumnsType} from 'ant-design-vue';
+
+export default defineComponent({
+  name: 'queryindex',
+  components: {DragWhere, DragBase, DragMeasure},
+  setup() {
+    const route = useRoute();
+    const expand = ref(false);
+    const formRef = ref<FormInstance>();
+    const measure = ref<typeof DragMeasure>();
+    const base = ref<typeof DragBase>();
+    const where = ref<typeof DragWhere>();
+    const formState = ref({
+      tempId: '',
+      tempName: '',
+      tempNo: '',
+      subId: null,
+      remark: '',
+      measures : [],
+      wheres : [],
+      bases : []
+    });
+
+    const onFinish = () => {
+      if(formRef.value == undefined){
+        return;
+      }
+      formRef.value.validate().then(() => {
+        formState.value.measures = (measure.value as any).getTagList();
+        formState.value.wheres = (where.value as any).getTagList();
+        formState.value.bases = (base.value as any).getTagList();
+        console.log("formState", formState);
+
+        save('system/menu/saveMenu', formState.value).then(result => {
+          console.log(result);
+        });
+      });
+    }
+
+    const onChangeMeasure = (list) => {
+      console.log("onChangeMeasure", arguments, list);
+      formState.value.measures = list;
+      readerTable();
+    }
+    const onChangeWhere = (list) => {
+      console.log("onChangeWhere", arguments, list);
+      formState.value.wheres = list;
+    }
+    const onChangeBase = (list) => {
+      console.log("onChangeBase", arguments, list);
+      formState.value.bases = list;
+      readerTable();
+    }
+
+    const dataList = ref([]);
+    const columns : any= ref([]);
+    const readerTable = () =>{
+      columns.value = getTableColumns(formState.value.bases, formState.value.measures);
+    }
+    const getTableColumns = (bases, measures) =>{
+      let rtns: TableColumnsType = [];
+      bases.concat(measures).forEach((it)=>{
+        rtns.push({
+          title: it.fieldAlias?it.fieldAlias:it.fieldName,
+          dataIndex: it.fieldId + it.disOrder,
+          key: it.fieldId + it.disOrder,
+          align:"center"});
+      });
+      return rtns;
+    }
+
+    const labelCol = { style: { width: '100px' } };
+    return {
+      route,
+      expand,
+      onFinish,
+      formRef,
+      formState,
+      labelCol,
+      measure,
+      where,
+      base,
+      onChangeMeasure,
+      onChangeWhere,
+      onChangeBase,
+
+      dataList,
+      columns
+    };
+  },
+  created() {
+
+  },
+  activated() {
+
+  }
+});
+</script>
+
+<style lang="less" scoped>
+  .ant-form-item{
+    margin: 0 0 10px !important;
+  }
+  .ant-form-item-label label{
+    width: 100% !important;
+    background-color: #2dd36f;
+  }
+
+  .query-index{
+    display: flex;
+    flex-direction: row;
+    height: 100%;
+    background-color: white;
+  }
+  .query-index-tree{
+    min-width: 200px;
+    border-right: 2px solid #f2f2f2;
+    margin-right: 5px;
+  }
+  .query-index-content{
+    flex-grow: 1;
+    display: flex;
+    flex-direction: column;
+  }
+  .query-index-form{
+    padding: 15px 10px 10px 15px;
+  }
+  .query-index-table{
+    flex-grow: 1;
+  }
+  .tabs-view-content{
+    padding: 0px;
+  }
+</style>