Pārlūkot izejas kodu

完成页面goLineData/timeData

Yue 3 gadi atpakaļ
vecāks
revīzija
b34fc1438c
30 mainītis faili ar 1019 papildinājumiem un 193 dzēšanām
  1. 3 1
      auto-imports.d.ts
  2. 3 0
      components.d.ts
  3. BIN
      public/media/table/01.gif
  4. BIN
      public/media/table/01.png
  5. BIN
      public/media/table/02.gif
  6. BIN
      public/media/table/02.png
  7. 9 0
      src/assets/sass/_common.scss
  8. 15 4
      src/assets/sass/_table.scss
  9. 2 1
      src/assets/sass/style.scss
  10. 4 3
      src/components/Table/VbDataTable.vue
  11. 5 10
      src/components/Table/table-partials/Loading.vue
  12. 6 5
      src/components/Table/table-partials/TableFooter.vue
  13. 10 11
      src/components/Table/table-partials/table-content/TableContent.vue
  14. 20 1
      src/components/Table/table-partials/table-content/table-body/TableBodyRow.vue
  15. 20 3
      src/components/Table/table-partials/table-content/table-fixed/TableFixed.vue
  16. 18 1
      src/components/Table/table-partials/table-content/table-fixed/TableRightFixed.vue
  17. 10 23
      src/components/Table/table-partials/table-content/table-footer/TablePagination.vue
  18. 3 1
      src/components/Table/table-partials/table-content/table-head/TableHeadRow.vue
  19. 1 2
      src/components/Tree/OrgCompany.vue
  20. 15 7
      src/components/charts/BaseChart.vue
  21. 24 3
      src/components/select/DateRangeSelect.vue
  22. 260 0
      src/components/select/DySearchSelect.vue
  23. 63 81
      src/components/select/DySelect.vue
  24. 9 2
      src/components/select/DySelectTree.vue
  25. 5 1
      src/core/charts/chartHelper.ts
  26. 1 0
      src/core/charts/chartOption.ts
  27. 1 1
      src/layouts/Icon.vue
  28. 3 3
      src/views/goLineData/oilFumeConcentration.vue
  29. 506 2
      src/views/goLineData/timeData.vue
  30. 3 27
      src/views/overAnalysis/overTime.vue

+ 3 - 1
auto-imports.d.ts

@@ -4,7 +4,9 @@
 // Generated by unplugin-auto-import
 export {}
 declare global {
-  const EffectScope: typeof import('vue')['EffectScope']
+
+}
+onst EffectScope: typeof import('vue')['EffectScope']
   const computed: typeof import('vue')['computed']
   const createApp: typeof import('vue')['createApp']
   const customRef: typeof import('vue')['customRef']

+ 3 - 0
components.d.ts

@@ -18,6 +18,7 @@ declare module '@vue/runtime-core' {
     Card4: typeof import('./src/components/cards/Card4.vue')['default']
     CodeHighlighter: typeof import('./src/components/highlighters/CodeHighlighter.vue')['default']
     CodeHighlighter2: typeof import('./src/components/highlighters/CodeHighlighter2.vue')['default']
+    copy: typeof import('./src/components/select/DySelect copy.vue')['default']
     CreditBalance: typeof import('./src/components/customers/cards/overview/CreditBalance.vue')['default']
     DateRangeSelect: typeof import('./src/components/select/DateRangeSelect.vue')['default']
     DateSelect: typeof import('./src/components/select/DateSelect.vue')['default']
@@ -26,10 +27,12 @@ declare module '@vue/runtime-core' {
     Dropdown3: typeof import('./src/components/dropdown/Dropdown3.vue')['default']
     Dropdown4: typeof import('./src/components/dropdown/Dropdown4.vue')['default']
     DynamicTreeSelect: typeof import('./src/components/select/DynamicTreeSelect.vue')['default']
+    DySearchSelect: typeof import('./src/components/select/DySearchSelect.vue')['default']
     DySelect: typeof import('./src/components/select/DySelect.vue')['default']
     DySelectTree: typeof import('./src/components/select/DySelectTree.vue')['default']
     DySelectTree2: typeof import('./src/components/select/DySelectTree2.vue')['default']
     Earnings: typeof import('./src/components/customers/cards/statments/Earnings.vue')['default']
+    ElA: typeof import('element-plus/es')['ElA']
     ElButton: typeof import('element-plus/es')['ElButton']
     ElCol: typeof import('element-plus/es')['ElCol']
     ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']

BIN
public/media/table/01.gif


BIN
public/media/table/01.png


BIN
public/media/table/02.gif


BIN
public/media/table/02.png


+ 9 - 0
src/assets/sass/_common.scss

@@ -0,0 +1,9 @@
+dl{
+  margin-bottom: 8px;
+  dt{
+    white-space: nowrap;
+  }
+  dd{
+    margin: 0;
+  }
+}

+ 15 - 4
src/assets/sass/_table.scss

@@ -1,5 +1,6 @@
 .vb-table{
   position: relative;
+  --table-footer-height:50px;
   table.table{
     table-layout: fixed;
     width: 100%;
@@ -30,23 +31,33 @@
     }
   }
 
+  .table-footer{
+    height: var(--table-footer-height);
+    align-items:center;
+  }
+
   .fixed-columns, .fixed-columns-right {
 
     position: absolute;
     top: 0;
-    height: 100%;
+    height:calc(100% - var(--table-footer-height) - 10px);
     background-color: #fff;
     box-sizing: border-box;
     z-index: 1;
     overflow: hidden!important;
-
-    
+    &.no-shadow{
+      box-shadow: none;
+    }
   }
   .fixed-columns{
-    left:0
+    left:0;
+    box-shadow: 20px -2px 10px -13px #ddd  ;
+   
   }
+  
   .fixed-columns-right{
     right: 0;
+    box-shadow: -20px -2px 10px -13px #ddd  ;
   }
   tr.text-center{
     th,td{

+ 2 - 1
src/assets/sass/style.scss

@@ -13,4 +13,5 @@
 @import "./core/layout/base/layout";
 @import "layout/layout";
 
-@import "table"
+@import "table";
+@import "common";

+ 4 - 3
src/components/Table/VbDataTable.vue

@@ -88,7 +88,7 @@ const table = ref()
 const dataToDisplay = computed(() => {
   let data = []
   if (props.url) {
-    data = remoteData.value
+    data = remoteData.value || []
   } else if (props.data) {
     data = props.data
   }
@@ -113,7 +113,6 @@ const totalItems = computed(() => {
 })
 
 const pageChange = (page: number) => {
-  currentPage.value = page
   emits("page-change", page)
   remote()
 }
@@ -138,7 +137,6 @@ const onItemChange = (isChecked: boolean, v: any, row: any) => {
 function remote() {
   loading.value = true
   remoteData.value = []
-  remoteTotal.value = 0
 
   const configs = Object.assign({ url: props.url, successAlert: false }, props.configs, {
     data: {
@@ -185,6 +183,7 @@ function search(params?: any) {
 watch(
   () => itemsInTable.value,
   (val) => {
+    console.log("WATCH_I")
     currentPage.value = 1
     emits("on-items-per-page-change", val)
     search()
@@ -194,6 +193,8 @@ watch(
 watch(
   () => props.queryParms,
   (val) => {
+    console.log("WATCH_Q")
+
     currentPage.value = 1
     queryParms.value = val
     search()

+ 5 - 10
src/components/Table/table-partials/Loading.vue

@@ -1,16 +1,11 @@
+<script setup lang="ts"></script>
 <template>
-  <div class="overlay-layer card-rounded bg-dark bg-opacity-5">
+  <div
+    class="overlay-layer card-rounded bg-white bg-opacity-5 w-100 position-absolute bottom-0 d-flex justify-content-center align-items-center"
+    style="height: calc(100% - 50px)"
+  >
     <div class="spinner-border text-primary" role="status">
       <span class="visually-hidden">加载中...</span>
     </div>
   </div>
 </template>
-
-<script lang="ts">
-import { defineComponent } from "vue"
-
-export default defineComponent({
-  name: "kt-loading",
-  components: {},
-})
-</script>

+ 6 - 5
src/components/Table/table-partials/TableFooter.vue

@@ -27,6 +27,7 @@ const props = withDefaults(
   }
 )
 const emits = defineEmits<{
+  (e: "update:currentPage", v: number): void
   (e: "update:itemsPerPage", v: number): void
   (e: "page-change", v: number): void
 }>()
@@ -46,9 +47,10 @@ watch(
     page.value = 1
   }
 )
+
 const pageChange = (newPage: number) => {
-  page.value = newPage
-  emits("page-change", page.value)
+  emits("page-change", newPage)
+  emits("update:currentPage", newPage)
 }
 
 const itemsCountInTable: WritableComputedRef<number> = computed({
@@ -66,13 +68,12 @@ const pageCount = computed(() => {
 })
 </script>
 <template>
-  <div class="row" v-if="!loading && pagination">
+  <div class="row table-footer" v-if="!loading && pagination && pageCount > 0">
     <TablePagination
-      v-if="pageCount > 0"
+      v-model:currentPage="page"
       :total-pages="pageCount"
       :max-visible-buttons="maxVisibleButtons"
       :per-page="itemsPerPage"
-      v-model:current-page="page"
       @page-change="pageChange"
     />
     <TableItemsPerPageSelect

+ 10 - 11
src/components/Table/table-partials/table-content/TableContent.vue

@@ -44,7 +44,7 @@ const props = withDefaults(
     fixedNumber: 0,
     fixedRightNumber: 0,
     emptyTableText: "未查询到数据",
-    tableClass: "table-hover table-bordered table-rounded table-striped  table-row-dashed",
+    tableClass: "table-bordered table-rounded table-striped  table-row-dashed",
     rowSpanSuffix: "_rowSpan",
   }
 )
@@ -155,12 +155,12 @@ defineExpose({
     :body-class="bodyClass"
     :td-tr-class="tdTrClass"
   >
-    <template v-for="(_, name) in $slots" #[name]="{ row: item }">
+    <template v-for="(_, name) in $slots" v-slot:[name]="{ row: item }">
       <slot :name="name" :row="item" />
     </template>
   </TableFixed>
 
-  <div class="table-responsive">
+  <div class="table-responsive position-relative">
     <table
       ref="table"
       class="table align-middle fs-6 gy-5 no-footer"
@@ -206,15 +206,14 @@ defineExpose({
           <slot v-if="!name.toString().endsWith('_header')" :name="name" :row="item" />
         </template>
       </TableBodyRow>
-      <template v-if="data.length == 0 && !loading">
-        <tr class="odd">
-          <td colspan="7" class="dataTables_empty text-center text-primary">
-            {{ emptyTableText }}
-          </td>
-        </tr>
-      </template>
-      <Loading v-if="loading" />
+      <tr v-if="loading" style="height: 50px"></tr>
     </table>
+    <template v-if="data.length == 0 && !loading">
+      <div class="text-center text-primary mt-10 mb-5 fs-4">
+        {{ emptyTableText }}
+      </div>
+    </template>
+    <Loading v-if="loading" />
   </div>
   <TableFixedRight
     v-if="props.fixedColumn && props.fixedRightNumber > 0"

+ 20 - 1
src/components/Table/table-partials/table-content/table-body/TableBodyRow.vue

@@ -88,7 +88,26 @@ const tdClass = (column: Header) => {
           </div>
         </td>
         <template v-for="(column, j) in header" :key="j">
-          <td :style="tdStyle(column)" :class="tdClass(column)" :rowspan="row[column.field + props.rowSpanSuffix]">
+          <td
+            v-if="row[column.field + props.rowSpanSuffix]"
+            :style="tdStyle(column)"
+            :class="tdClass(column)"
+            :rowspan="`${row[column.field + props.rowSpanSuffix] ? row[column.field + props.rowSpanSuffix] : ''}`"
+          >
+            <template v-if="$slots[column.field]">
+              <slot :name="`${column.field}`" :row="row">
+                {{ row }}
+              </slot>
+            </template>
+            <template v-else>
+              {{ row[column.field] }}
+            </template>
+          </td>
+          <td
+            v-else-if="row[column.field + props.rowSpanSuffix] != 0"
+            :style="tdStyle(column)"
+            :class="tdClass(column)"
+          >
             <template v-if="$slots[column.field]">
               <slot :name="`${column.field}`" :row="row">
                 {{ row }}

+ 20 - 3
src/components/Table/table-partials/table-content/table-fixed/TableFixed.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, defineProps, withDefaults, defineEmits, watch } from "vue"
+import { ref, defineProps, withDefaults, defineEmits, watch, onBeforeUnmount } from "vue"
 import type { Sort, Header } from "@/components/Table/table-partials/models"
 import TableHeadRow from "@/components/Table/table-partials/table-content/table-head/TableHeadRow.vue"
 import TableBodyRow from "@/components/Table/table-partials/table-content/table-body/TableBodyRow.vue"
@@ -73,7 +73,15 @@ const onSelectItemsChange = (isChecked: boolean, v: any, row: any) => {
 const onSort = (v: Sort) => {
   emits("on-sort", v)
 }
-
+function onScroll() {
+  tableBox.value.className = tableBox.value.className.replace("no-shadow", "")
+  if (props.table.parentElement) {
+    const left = props.table.parentElement.scrollLeft
+    if (left == 0) {
+      tableBox.value.className += " no-shadow"
+    }
+  }
+}
 watch(
   () => props.data,
   () => {
@@ -88,6 +96,7 @@ watch(
     //console.log("allSelectedItems:", allSelectedItems.value)
   }
 )
+
 watch(
   () => props.table,
   (val: HTMLElement) => {
@@ -100,11 +109,19 @@ watch(
       tableBox.value.style.width = width + "px"
       tableBox.value.firstElementChild.style.width = val.clientWidth + "px"
     }
+    if (props.table.parentElement) {
+      props.table.parentElement.addEventListener("scroll", onScroll, true)
+    }
   }
 )
+onBeforeUnmount(() => {
+  if (props.table?.parentElement) {
+    props.table.parentElement.removeEventListener("scroll", onScroll, true)
+  }
+})
 </script>
 <template>
-  <div class="fixed-columns" ref="tableBox">
+  <div class="fixed-columns no-shadow" ref="tableBox">
     <table class="table align-middle fs-6 gy-5 no-footer" :class="tableClass">
       <TableHeadRow
         @on-sort="onSort"

+ 18 - 1
src/components/Table/table-partials/table-content/table-fixed/TableRightFixed.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { ref, defineProps, withDefaults, defineEmits, watch } from "vue"
+import { ref, defineProps, withDefaults, defineEmits, watch, onBeforeUnmount } from "vue"
 import type { Sort, Header } from "@/components/Table/table-partials/models"
 import TableHeadRow from "@/components/Table/table-partials/table-content/table-head/TableHeadRow.vue"
 import TableBodyRow from "@/components/Table/table-partials/table-content/table-body/TableBodyRow.vue"
@@ -37,6 +37,15 @@ const tableBox = ref()
 const onSort = (v: Sort) => {
   emits("on-sort", v)
 }
+function onScroll() {
+  tableBox.value.className = tableBox.value.className.replace("no-shadow", "")
+  if (props.table.parentElement) {
+    const left = props.table.parentElement.scrollLeft
+    if (left >= props.table.clientWidth - props.table.parentElement.clientWidth) {
+      tableBox.value.className += " no-shadow"
+    }
+  }
+}
 watch(
   () => props.table,
   (val: HTMLElement) => {
@@ -53,8 +62,16 @@ watch(
       tableBox.value.firstElementChild.style.width = val.clientWidth + "px"
       tableBox.value.firstElementChild.style.transform = `translateX(-${val.clientWidth - width}px)`
     }
+    if (props.table.parentElement) {
+      props.table.parentElement.addEventListener("scroll", onScroll, true)
+    }
   }
 )
+onBeforeUnmount(() => {
+  if (props.table?.parentElement) {
+    props.table.parentElement.removeEventListener("scroll", onScroll, true)
+  }
+})
 </script>
 <template>
   <div ref="tableBox" class="fixed-columns-right">

+ 10 - 23
src/components/Table/table-partials/table-content/table-footer/TablePagination.vue

@@ -12,6 +12,7 @@ const props = withDefaults(
   { maxVisibleButtons: 5 }
 )
 const emits = defineEmits<{
+  (e: "update:currentPage", v: number): void
   (e: "page-change", v: number): void
 }>()
 
@@ -60,26 +61,12 @@ const isInLastPage = computed(() => {
   return currentPage.value === props.totalPages
 })
 
-const onClickFirstPage = () => {
-  console.log("FIRST", currentPage.value)
-  emits("page-change", 1)
-}
-const onClickPreviousPage = () => {
-  console.log("PREV", currentPage.value)
-  emits("page-change", currentPage.value - 1)
-}
-const onClickPage = (page: number) => {
-  console.log("CLICK", currentPage.value)
+const clickPage = (page: number) => {
+  //console.log("PAGE_TO_CHANGE", currentPage.value, "==>", page)
+  emits("update:currentPage", page)
   emits("page-change", page)
 }
-const onClickNextPage = () => {
-  console.log("NEXT", currentPage.value)
-  emits("page-change", currentPage.value + 1)
-}
-const onClickLastPage = () => {
-  console.log("LAST", currentPage.value)
-  emits("page-change", props.totalPages)
-}
+
 const isPageActive = (page: number) => {
   return currentPage.value === page
 }
@@ -93,7 +80,7 @@ const isPageActive = (page: number) => {
           :class="{ disabled: isInFirstPage }"
           :style="{ cursor: !isInFirstPage ? 'pointer' : 'auto' }"
         >
-          <a class="page-link" @click="onClickFirstPage">
+          <a class="page-link" @click="clickPage(1)">
             <span class="svg-icon">
               <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr079.svg')" />
             </span>
@@ -105,7 +92,7 @@ const isPageActive = (page: number) => {
           :class="{ disabled: isInFirstPage }"
           :style="{ cursor: !isInFirstPage ? 'pointer' : 'auto' }"
         >
-          <a class="page-link" @click="onClickPreviousPage">
+          <a class="page-link" @click="clickPage(currentPage - 1)">
             <span class="svg-icon">
               <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr074.svg')" />
             </span>
@@ -121,7 +108,7 @@ const isPageActive = (page: number) => {
           :style="{ cursor: !page.isDisabled ? 'pointer' : 'auto' }"
           :key="i"
         >
-          <a class="page-link" @click="onClickPage(page.name)">
+          <a class="page-link" @click="clickPage(page.name)">
             {{ page.name }}
           </a>
         </li>
@@ -131,7 +118,7 @@ const isPageActive = (page: number) => {
           :class="{ disabled: isInLastPage }"
           :style="{ cursor: !isInLastPage ? 'pointer' : 'auto' }"
         >
-          <a class="paginate_button page-link" @click="onClickNextPage">
+          <a class="paginate_button page-link" @click="clickPage(currentPage + 1)">
             <span class="svg-icon">
               <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr071.svg')" />
             </span>
@@ -143,7 +130,7 @@ const isPageActive = (page: number) => {
           :class="{ disabled: isInLastPage }"
           :style="{ cursor: !isInLastPage ? 'pointer' : 'auto' }"
         >
-          <a class="paginate_button page-link" @click="onClickLastPage">
+          <a class="paginate_button page-link" @click="clickPage(totalPages)">
             <span class="svg-icon">
               <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr080.svg')" />
             </span>

+ 3 - 1
src/components/Table/table-partials/table-content/table-head/TableHeadRow.vue

@@ -108,7 +108,9 @@ const thClass = (column: Header) => {
       </th>
       <template v-for="(column, i) in header" :key="i">
         <th @click="onSort(column.field, column.isSort)" :class="thClass(column)" :style="thStyle(column)">
-          <slot v-if="$slots[column.field + '_header']" :name="`${column.field}_header`" :row="column"></slot>
+          <slot v-if="$slots[column.field + '_header']" :name="`${column.field}_header`" :row="{ column }">
+            {{ column }}
+          </slot>
           <span v-else>{{ column.name }}</span>
           <span
             v-if="columnFieldAndOrder.label === column.field && column.isSort"

+ 1 - 2
src/components/Tree/OrgCompany.vue

@@ -113,8 +113,7 @@ function getIconClass(node: any) {
   return str
 }
 function onNodeClick(nodeData: any) {
-  //const nodeData = node.data
-  console.log(nodeData)
+  //console.log(nodeData)
   emits("selected", nodeData.key.split("_")[1], nodeData.key)
   emits("node-click", nodeData)
 }

+ 15 - 7
src/components/charts/BaseChart.vue

@@ -5,6 +5,7 @@ import {
   defineProps,
   withDefaults,
   defineEmits,
+  defineExpose,
   onBeforeMount,
   onMounted,
   onBeforeUnmount,
@@ -17,6 +18,7 @@ import * as echarts from "echarts"
 
 const props = withDefaults(
   defineProps<{
+    value?: any
     type: string
     data: any
     options?: any
@@ -33,11 +35,11 @@ const props = withDefaults(
   }
 )
 const emits = defineEmits<{
-  (e: "update:value", v: string): void
+  (e: "update:value", v: any): void
   (e: "renderCompalte", v: string): void
   (e: "resize"): void
 }>()
-const chartStyle = ref({ width: "800px", height: "300px" })
+const chartStyle = ref({ width: "100%", height: "300px" })
 const chartEl = ref<HTMLElement>()
 const chart = shallowRef()
 // const registerTheme = () => {
@@ -73,15 +75,19 @@ const resetSize = () => {
 }
 
 function init() {
-  const chartOption = parser[props.type as string](props.data, props.options)
-  console.log("====>", chartOption, props.options)
-
+  //console.log("====>", chartOption, props.options)
   if (props.theme) {
     chart.value = echarts.init(chartEl.value as HTMLElement, props.theme)
   } else {
     chart.value = echarts.init(chartEl.value as HTMLElement)
   }
 
+  emits("update:value", chart)
+}
+function renderChart() {
+  const chartOption = parser[props.type as string](props.data, props.options)
+  console.log("CHART_OPTIONS", chartOption, props.type, "==>", props.options, "<==>", props.data)
+
   chart.value.clear()
   chart.value.showLoading()
   chart.value.setOption(chartOption)
@@ -90,7 +96,6 @@ function init() {
     //自适应大小
     chart.value.resize()
   }
-  emits("renderCompalte", chartOption.iTitle)
 }
 
 onBeforeMount(() => {
@@ -100,6 +105,7 @@ onBeforeMount(() => {
 })
 onMounted(() => {
   init()
+  renderChart()
 })
 onBeforeUnmount(() => {
   if (!chart.value) {
@@ -111,10 +117,12 @@ onBeforeUnmount(() => {
 watch(
   () => props.data,
   () => {
-    init()
+    renderChart()
+    chart.value.resize()
   },
   { immediate: false }
 )
+defineExpose({ chart })
 </script>
 
 <template>

+ 24 - 3
src/components/select/DateRangeSelect.vue

@@ -9,7 +9,7 @@ const props = withDefaults(
   defineProps<{
     dateValue: [Date, Date]
     selectValue: number | string
-    dateArray: Array<DateSelectOption>
+    dateArray?: Array<DateSelectOption>
     label?: string
     rangeSeparator?: string
     placeholder?: string
@@ -22,8 +22,29 @@ const props = withDefaults(
     dateClass?: string
   }>(),
   {
-    value: () => {
-      return [new Date(), new Date()]
+    dateArray: () => {
+      return [
+        {
+          text: "当日",
+          value: [new Date(), new Date()],
+        },
+        {
+          text: "近三日",
+          value: [moment(new Date()).add(-3, "d").toDate(), new Date()],
+        },
+        {
+          text: "近一周",
+          value: [moment(new Date()).add(-7, "d").toDate(), new Date()],
+        },
+        {
+          text: "近一月",
+          value: [moment(new Date()).add(-1, "M").toDate(), new Date()],
+        },
+        {
+          text: "本月",
+          value: [moment(new Date()).startOf("month").toDate(), new Date()],
+        },
+      ]
     },
     label: "日期",
     placeholder: "请选择日期",

+ 260 - 0
src/components/select/DySearchSelect.vue

@@ -0,0 +1,260 @@
+<script setup lang="ts">
+import { ref, toRefs, defineProps, withDefaults, defineEmits, onMounted } from "vue"
+import type { AxiosRequestConfig } from "axios"
+import Rs from "@/core/services/RequestService"
+export interface SelectOptionProp {
+  key: string | number
+  value: string | number
+  label?: string | number //若不设置则默认与value相同
+  disabled?: boolean
+}
+export interface SelectProp {
+  value: string | number | Array<string> | Array<number>
+  name?: string
+  url?: string
+  method?: string
+  configs?: AxiosRequestConfig
+  formatFunction?: (data: any) => SelectOptionProp
+  staticOptions?: Array<SelectOptionProp>
+  data?: Array<any>
+  placeholder?: string
+  disabled?: boolean
+  isSearch?: boolean
+  autocomplete?: string
+  size?: "large" | "default" | "small" //输入框尺寸
+  effect?: "dark " | "light" //Tooltip 主题,内置了 dark / light 两种
+  clearable?: boolean //是否可以清空选项
+  clearIcon?: string //是否可以清空选项
+  multiple?: boolean //是否多选
+  multipleLimit?: number //为0时不限制
+  placement?:
+    | "top"
+    | "top-start"
+    | "top-end"
+    | "bottom"
+    | "bottom-start"
+    | "bottom-end"
+    | "left"
+    | "left-start"
+    | "left-end"
+    | "right"
+    | "right-start"
+    | "right-end" //下拉框出现的位置
+  filterable?: boolean //Select 组件是否可筛选
+  filterMethod?: (v: string, o: Array<SelectOptionProp>) => any //自定义筛选方法
+  remote?: boolean //其中的选项是否从服务器远程加载
+  remoteShowSuffix?: boolean //远程搜索方法显示后缀图标
+  remoteMethod?: (v: string, o: Array<SelectOptionProp>) => any //远程搜索方法显示后缀图标
+  formatRemoteData?: (v: any) => any
+  loading?: boolean //是否正在从远程获取数据
+  loadingText?: string //从服务器加载内容时显示的文本
+  noMatchText?: string //搜索条件无匹配时显示的文字,也可以使用 empty 插槽设置
+  noDataText?: string //搜索条件无匹配时显示的文字,也可以使用 empty 插槽设置
+  popperClass?: string //选择器下拉菜单的自定义类名
+  suffixIcon?: string //后缀图标组件
+  reserveKeyword?: boolean //当 multiple 和 filter被设置为 true 时,是否在选中一个选项后保留当前的搜索关键词
+  defaultFirstOption?: boolean //是否在输入框按下回车时,选择第一个匹配项。 需配合 filterable 或 remote 使用
+  popperAppendToBody?: boolean //是否将弹出框插入至 body 元素 当弹出框的位置出现问题时,你可以尝试将该属性设置为false
+  teleported?: boolean //该下拉菜单是否使用teleport插入body元素
+  persistent?: boolean //当下拉选择器未被激活并且persistent设置为false,选择器会被删除。
+  automatiDropdown?: boolean //对于不可过滤的 Select 组件,此属性决定是否在输入框获得焦点后自动弹出选项菜单
+  fitInputWidth?: boolean //下拉框的宽度是否与输入框相同
+  validateEvent?: boolean //是否触发表单验证
+}
+const props = withDefaults(defineProps<SelectProp>(), {
+  value: "",
+  url: "",
+  method: "get",
+  configs: undefined,
+  multiple: false,
+  multipleLimit: 0,
+  filterable: false,
+  remote: false,
+  formatRemoteData: (v: any) => {
+    return v
+  },
+  loading: false,
+  loadingText: "加载中...",
+  noMatchText: "没有匹配项",
+  noDataText: "未查询到数据",
+  isSearch: false,
+  disabled: false,
+  clearable: false,
+  clearIcon: "CircleClose",
+  effect: "light",
+  size: "default",
+  placement: "bottom-start",
+  autocomplete: "off",
+  placeholder: "请选择",
+  popperClass: "",
+  suffixIcon: "ArrowDown",
+  defaultFirstOption: false,
+  reserveKeyword: true,
+  popperAppendToBody: true,
+  teleported: true,
+  persistent: true,
+  automatiDropdown: false,
+  fitInputWidth: false,
+  validateEvent: false,
+})
+const emits = defineEmits<{
+  (e: "update:value", v: string | number | Array<string> | Array<number>): void
+  (e: "change", v: string | number | Array<string> | Array<number>): void
+  (e: "visible-change", v: boolean): void //下拉框出现/隐藏时触发 出现则为 true,隐藏则为 false
+  (e: "remove-tag", v: string | number | Array<string> | Array<number>): void
+  (e: "clear"): void
+  (e: "blur", v: FocusEvent): void
+  (e: "focus", v: FocusEvent): void
+}>()
+const { value, url } = toRefs(props)
+const loading = ref(props.loading)
+const remote = ref(props.remote)
+const filterable = ref(props.filterable)
+const staticOptions = ref<Array<SelectOptionProp>>()
+const options = ref<Array<SelectOptionProp>>(staticOptions.value ?? [])
+
+let remoteFunction: (v: string) => void
+
+function init() {
+  staticOptions.value = Object.assign([], props.staticOptions || [], formatSelectData(props.data) ?? [])
+  if (props.remoteMethod) {
+    filterable.value = true
+    remote.value = true
+    remoteFunction = (v: string) => {
+      props.remoteMethod && props.remoteMethod(v, options.value)
+      loading.value = false
+    }
+  } else if (props.url) {
+    filterable.value = true
+    remote.value = true
+    const configs = Object.assign({}, { url: url.value, successAlert: false }, props.configs)
+
+    remoteFunction = (query: string) => {
+      options.value = Object.assign([], staticOptions.value ?? [])
+      console.log("======>", options.value, "=", staticOptions.value)
+
+      if (!props.isSearch || query) {
+        loading.value = true
+        if (props.method.toLowerCase() == "get") {
+          Rs.get(configs).then((res: any) => {
+            processData(res.data)
+          })
+        } else if (props.method.toLowerCase() == "post") {
+          Rs.get(configs).then((res: any) => {
+            processData(res.data)
+          })
+        }
+      }
+      function processData(data: any) {
+        let result = formatSelectData(data)
+        if (query) {
+          result = result.filter((v: SelectOptionProp) => {
+            return (
+              (v.label as string).toLowerCase().includes(query.toLowerCase()) ||
+              (v.value as string).toLowerCase().includes(query.toLowerCase())
+            )
+          })
+        }
+        options.value.push(...result)
+        loading.value = false
+      }
+    }
+  }
+}
+
+function formatSelectData(v: any) {
+  const result: Array<SelectOptionProp> = []
+  const data = props.formatRemoteData && typeof props.formatRemoteData == "function" ? props.formatRemoteData(v) : v
+  if (data && data.length) {
+    data.forEach((v: any) => {
+      let item = {} as SelectOptionProp
+      if (props.formatFunction) {
+        item = props.formatFunction(v)
+      } else {
+        item = Object.assign({}, v)
+        if ("value" in v) {
+          item.key = v.value + ""
+          item.value = v.value + ""
+        } else if ("code" in v) {
+          item.key = v.code + ""
+          item.value = v.code + ""
+        }
+        if ("label" in v) {
+          item.label = v.label + ""
+        } else if ("name" in v) {
+          item.label = v.name + ""
+        } else if ("title" in v) {
+          item.label = v.title + ""
+        }
+      }
+      result.push(item)
+    })
+  }
+
+  return result
+}
+function onChange(val: string | number | Array<string> | Array<number>) {
+  emits("update:value", val)
+  emits("change", val)
+}
+function onRemoveTag(val: string | number | Array<string> | Array<number>) {
+  emits("remove-tag", val)
+}
+function onVisibleChange(val: boolean) {
+  //下拉框出现/隐藏时触发 出现则为 true,隐藏则为 false
+  emits("visible-change", val)
+}
+function onBlur(v: FocusEvent) {
+  emits("blur", v)
+}
+function onFocus(v: FocusEvent) {
+  emits("focus", v)
+}
+function onClear() {
+  emits("clear")
+}
+onMounted(() => {
+  init()
+})
+</script>
+<template>
+  <el-select
+    v-model="value"
+    :placeholder="placeholder"
+    :disabled="disabled"
+    :multiple="multiple"
+    :multiple-limit="multipleLimit"
+    :clearable="clearable"
+    :clear-icon="clearIcon"
+    :suffix-icon="suffixIcon"
+    :popper-class="popperClass"
+    :autocomplete="autocomplete"
+    :size="size"
+    :effect="effect"
+    :placement="placement"
+    :filterable="filterable"
+    :remote="remote"
+    :remote-method="remoteFunction"
+    :remote-show-suffix="remoteShowSuffix"
+    :loading="loading"
+    :loading-text="loadingText"
+    :no-match-text="noMatchText"
+    :no-data-text="noDataText"
+    :default-first-option="defaultFirstOption"
+    :popper-append-to-body="popperAppendToBody"
+    :reserve-keyword="reserveKeyword"
+    :teleported="teleported"
+    :persistent="persistent"
+    :automati-dropdown="automatiDropdown"
+    :fit-input-width="fitInputWidth"
+    :validate-event="validateEvent"
+    @change="onChange"
+    @remove-tag="onRemoveTag"
+    @visible-change="onVisibleChange"
+    @blur="onBlur"
+    @focus="onFocus"
+    @clear="onClear"
+  >
+    <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+  </el-select>
+</template>

+ 63 - 81
src/components/select/DySelect.vue

@@ -12,7 +12,9 @@ export interface SelectProp {
   value: string | number | Array<string> | Array<number>
   name?: string
   url?: string
+  method?: string
   configs?: AxiosRequestConfig
+  formatRemoteData?: (v: any) => any
   formatFunction?: (data: any) => SelectOptionProp
   staticOptions?: Array<SelectOptionProp>
   data?: Array<any>
@@ -38,15 +40,6 @@ export interface SelectProp {
     | "right"
     | "right-start"
     | "right-end" //下拉框出现的位置
-  filterable?: boolean //Select 组件是否可筛选
-  filterMethod?: (v: string, o: Array<SelectOptionProp>) => any //自定义筛选方法
-  remote?: boolean //其中的选项是否从服务器远程加载
-  remoteShowSuffix?: boolean //远程搜索方法显示后缀图标
-  remoteMethod?: (v: string, o: Array<SelectOptionProp>) => any //远程搜索方法显示后缀图标
-  loading?: boolean //是否正在从远程获取数据
-  loadingText?: string //从服务器加载内容时显示的文本
-  noMatchText?: string //搜索条件无匹配时显示的文字,也可以使用 empty 插槽设置
-  noDataText?: string //搜索条件无匹配时显示的文字,也可以使用 empty 插槽设置
   popperClass?: string //选择器下拉菜单的自定义类名
   suffixIcon?: string //后缀图标组件
   reserveKeyword?: boolean //当 multiple 和 filter被设置为 true 时,是否在选中一个选项后保留当前的搜索关键词
@@ -61,25 +54,22 @@ export interface SelectProp {
 const props = withDefaults(defineProps<SelectProp>(), {
   value: "",
   url: "",
+  method: "get",
   configs: undefined,
   multiple: false,
   multipleLimit: 0,
-  filterable: false,
-  remote: false,
-  loading: false,
-  loadingText: "加载中...",
-  noMatchText: "没有匹配项",
-  noDataText: "未查询到数据",
+  formatRemoteData: (v: any) => {
+    return v
+  },
   disabled: false,
-  clearable: false,
-  clearIcon: "CircleClose",
+  clearable: true,
+  //clearIcon: "CircleClose",
   effect: "light",
   size: "default",
   placement: "bottom-start",
   autocomplete: "off",
   placeholder: "请选择",
   popperClass: "",
-  suffixIcon: "ArrowDown",
   defaultFirstOption: false,
   reserveKeyword: true,
   popperAppendToBody: true,
@@ -98,71 +88,69 @@ const emits = defineEmits<{
   (e: "blur", v: FocusEvent): void
   (e: "focus", v: FocusEvent): void
 }>()
-const { value, url, remote, filterable, loading } = toRefs(props)
+const { value, url } = toRefs(props)
 const staticOptions = ref<Array<SelectOptionProp>>()
 const options = ref<Array<SelectOptionProp>>(staticOptions.value ?? [])
-let remoteFunction: (v: string) => void
+function loadData() {
+  options.value = Object.assign([], staticOptions.value ?? [])
 
-function init() {
-  staticOptions.value = Object.assign([], props.staticOptions || [], formatSelectData(props.data) ?? [])
-  if (props.remoteMethod) {
-    filterable.value = true
-    remote.value = true
-    remoteFunction = (v: string) => {
-      props.remoteMethod && props.remoteMethod(v, options.value)
-      loading.value = false
-    }
-  } else if (props.url) {
-    filterable.value = true
-    remote.value = true
-    const configs = Object.assign({}, { url: url.value, method: "get", successAlert: false }, props.configs)
-    remoteFunction = (query: string) => {
-      if (query) {
-        loading.value = true
-        Rs.request(configs).then((res: any) => {
-          options.value = staticOptions.value ?? []
-          const result = formatSelectData(res.data)
-          result.filter((v: SelectOptionProp) => {
-            return (
-              (v.label as string).toLowerCase().includes(query.toLowerCase()) ||
-              (v.value as string).toLowerCase().includes(query.toLowerCase())
-            )
-          })
-          options.value.push(...result)
-          loading.value = false
-        })
-      } else {
-        options.value = []
-      }
+  if (props.data) {
+    const result = formatSelectData(props.data)
+    options.value.push(...result)
+  }
+  if (props.url) {
+    const configs = Object.assign({}, { url: url.value, successAlert: false }, props.configs)
+
+    if (props.method.toLowerCase() == "get") {
+      Rs.get(configs).then((res: any) => {
+        processData(res.data)
+      })
+    } else if (props.method.toLowerCase() == "post") {
+      Rs.get(configs).then((res: any) => {
+        processData(res.data)
+      })
     }
   }
+  function processData(data: any) {
+    data = props.formatRemoteData && typeof props.formatRemoteData == "function" ? props.formatRemoteData(data) : data
+    const result = formatSelectData(data)
+    options.value.push(...result)
+  }
+}
+function init() {
+  staticOptions.value = Object.assign([], props.staticOptions || [], formatSelectData(props.data) ?? [])
+  loadData()
 }
 
 function formatSelectData(data: any) {
   const result: Array<SelectOptionProp> = []
-  data.forEach((v: any) => {
-    let item = {} as SelectOptionProp
-    if (props.formatFunction) {
-      item = props.formatFunction(v)
-    } else {
-      item = Object.assign({}, v)
-      if ("value" in v) {
-        item.key = v.value + ""
-        item.value = v.value + ""
-      } else if ("code" in v) {
-        item.key = v.code + ""
-        item.value = v.code + ""
-      }
-      if ("label" in v) {
-        item.label = v.label + ""
-      } else if ("name" in v) {
-        item.label = v.name + ""
-      } else if ("title" in v) {
-        item.label = v.title + ""
+
+  if (data && data.length) {
+    data.forEach((v: any) => {
+      let item = {} as SelectOptionProp
+      if (props.formatFunction) {
+        item = props.formatFunction(v)
+      } else {
+        item = Object.assign({}, v)
+        if ("value" in v) {
+          item.key = v.value + ""
+          item.value = v.value + ""
+        } else if ("code" in v) {
+          item.key = v.code + ""
+          item.value = v.code + ""
+        }
+        if ("label" in v) {
+          item.label = v.label + ""
+        } else if ("name" in v) {
+          item.label = v.name + ""
+        } else if ("title" in v) {
+          item.label = v.title + ""
+        }
       }
-    }
-    result.push(item)
-  })
+      result.push(item)
+    })
+  }
+
   return result
 }
 function onChange(val: string | number | Array<string> | Array<number>) {
@@ -204,14 +192,8 @@ onMounted(() => {
     :size="size"
     :effect="effect"
     :placement="placement"
-    :filterable="filterable"
-    :remote="remote"
-    :remote-method="remoteFunction"
-    :remote-show-suffix="remoteShowSuffix"
-    :loading="loading"
-    :loading-text="loadingText"
-    :no-match-text="noMatchText"
-    :no-data-text="noDataText"
+    :filterable="false"
+    :remote="false"
     :default-first-option="defaultFirstOption"
     :popper-append-to-body="popperAppendToBody"
     :reserve-keyword="reserveKeyword"

+ 9 - 2
src/components/select/DySelectTree.vue

@@ -51,9 +51,11 @@ const props = withDefaults(
   }
 )
 const emits = defineEmits<{
+  (e: "update:value", v: string): void
   (e: "select", v: any): void
 }>()
-const { value, staticOptions, url } = toRefs(props)
+const { staticOptions, url } = toRefs(props)
+const value = ref(props.value)
 const options = ref<Array<TreeOption>>(staticOptions.value ?? [])
 
 function formatData(data: any) {
@@ -88,12 +90,17 @@ function init() {
         const item = formatData(v)
         options.value.push(item)
       })
-      value.value = options.value[0].id
+      const val = options.value[0].id
+      emits("update:value", val)
+      value.value = val
     })
   }
 }
 
 function onSelect(data: any) {
+  console.log("data", data)
+
+  emits("update:value", data[props.optionMap.id])
   emits("select", data)
 }
 onMounted(() => {

+ 5 - 1
src/core/charts/chartHelper.ts

@@ -225,6 +225,8 @@ const lineBarSeriesOption = (chart: any, serieData: Array<any>, serieType: strin
   }
   let yAxis: any = {
     show: true,
+    name: getOption(chart, "yzTitle", ""),
+    nameTextStyle: { align: "auto" },
     type: "value",
     max: yAxisMax ? yAxisMax : null,
     axisLabel: { ...axisLabel, formatter: getOption(chartOptions, "yAxisFormat", undefined) },
@@ -235,6 +237,8 @@ const lineBarSeriesOption = (chart: any, serieData: Array<any>, serieType: strin
       alignWithLabel: true,
     },
   }
+  console.log("CHART_title", yAxis.name)
+
   const direction = getOption(chartOptions, "direction", false)
 
   if (direction) {
@@ -358,7 +362,7 @@ export default {
     //标题默认在图表下居中 ,使用远程数据 yzTitle 字段
     const title = {
       show: true,
-      text: getOption(_chart, "yzTitle", ""),
+      text: getOption(_chart, "title", "") || getOption(_chart, "yzTitle", ""),
     }
     const grid = Object.assign({}, defaultOption.SIZE.grid)
 

+ 1 - 0
src/core/charts/chartOption.ts

@@ -12,6 +12,7 @@ export default {
       right: 10,
       top: 10,
       bottom: 0,
+      width: 300,
       itemWidth: 10,
       itemHeight: 10,
       itemGap: 16,

+ 1 - 1
src/layouts/Icon.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { ref, onMounted } from "vue"
 import { getAssetPath } from "@/core/helpers/assets"
-import PreviewCode from "@/components/prismjs/PreviewCode .vue"
+//import PreviewCode from "@/components/prismjs/PreviewCode .vue"
 //import CodeHighlighter from "@/components/prismjs/CodeHighlighter .vue"
 const curSvg = ref("")
 const show = ref(false)

+ 3 - 3
src/views/goLineData/oilFumeConcentration.vue

@@ -9,12 +9,12 @@ function onSelected(selectedId: string, selectedKey: string) {
 </script>
 
 <template>
-  <el-row :gutter="20" class="h-100">
-    <el-col class="bg-white p-5 h-100" :span="6">
+  <el-row :gutter="20" class="h-100" style="max-height: calc(100vh - 170px)">
+    <el-col class="bg-white p-5 h-100 overflow-auto" :span="6">
       <OrgCompany class="grid-content" @selected="onSelected"></OrgCompany>
     </el-col>
     <el-col :span="18">
-      <div class="bg-white p-5 h-100">
+      <div class="bg-white p-5 h-100 overflow-auto">
         <span>{{ id }}</span>
         <br />
         <span>{{ key }}</span>

+ 506 - 2
src/views/goLineData/timeData.vue

@@ -1,7 +1,511 @@
 <script setup lang="ts">
-import { defineProps, reactive, ref, toRefs } from "vue"
+import { ref, onMounted } from "vue"
+import { getAssetPath } from "@/core/helpers/assets"
+import moment from "moment"
+import type { Header } from "@/components/Table/table-partials/models"
+import Rs from "@/core/services/RequestService"
+import { useRouter } from "vue-router"
+
+const router = useRouter()
+
+const cols = ref<Array<Header>>([
+  {
+    name: "区域",
+    field: "org_name",
+  },
+  {
+    name: "商户",
+    field: "company_name",
+  },
+  {
+    name: "营业规模",
+    field: "catering_scale_name",
+  },
+  {
+    name: "类型",
+    field: "type_name",
+  },
+  {
+    name: "营业状态",
+    field: "business_state",
+  },
+  {
+    name: "灶头数",
+    field: "stove_num",
+  },
+  {
+    name: "排口数",
+    field: "outlet_num",
+  },
+  {
+    name: "净化设施",
+    field: "device_name",
+  },
+  {
+    name: "净化器",
+    field: "purifier_online_state",
+  },
+  {
+    name: "风机",
+    field: "fan_online_state",
+  },
+  {
+    name: "监控仪状态",
+    field: "device_online_state",
+  },
+  {
+    name: "净化效能",
+    field: "clean_efficiency",
+  },
+  {
+    name: "油烟浓度mg/m3",
+    field: "smoke_density",
+  },
+  {
+    name: "监测时间",
+    width: 250,
+    field: "last_online_time",
+  },
+  {
+    name: "操作",
+    width: 150,
+    field: "action",
+  },
+])
+const size = ref<any>("default")
+const headData = ref<any>({
+  exceed_company_num: 0,
+  clean_low_num: 0,
+})
+const dySearchSelectStyle = { width: "120px" }
+const headType = ref(0)
+const exceedCompanyFlag = ref(false)
+const cleanFlag = ref(false)
+const companyName = ref("")
+const orgId = ref<string | null>(null)
+const operateStatus = ref("")
+const type = ref("")
+const cateringScale = ref("")
+const cateringStyle = ref("")
+//const monitoringType = ref("")
+//const levelType = ref("")
+//const warnType = ref([])
+//const abnormalState = ref([])
+//const current_date_range = ref(new Date())
+
+const queryParams = ref<any>({ exceed_company_flag: false, clean_flag: false })
+function query() {
+  const params = {
+    //monitoring_type: monitoringType.value,
+    company_name: companyName.value,
+    org_id: orgId.value,
+    operate_status: operateStatus.value,
+    type: type.value,
+    catering_scale: cateringScale.value,
+    catering_style: cateringStyle.value,
+    //warn_type: warnType.value,
+    //abnormal_state: abnormalState.value,
+    exceed_company_flag: exceedCompanyFlag.value,
+    clean_flag: cleanFlag.value,
+  }
+
+  const keys = Object.keys(params)
+  keys.forEach((key) => {
+    //console.log("----", key, "=", params[key])
+
+    if (params[key] == "" && key != "clean_flag" && key != "exceed_company_flag") {
+      //console.log("DELETE PAAMS ===>", params)
+      delete params[key]
+    }
+  })
+
+  queryParams.value = Object.assign({}, params)
+}
+function reset() {
+  //monitoringType.value = ""
+  type.value = ""
+  companyName.value = ""
+  //levelType.value = ""
+  orgId.value = ""
+  operateStatus.value = ""
+  type.value = ""
+  cateringScale.value = ""
+  cateringStyle.value = ""
+  //warnType.value = []
+  //abnormalState.value = []
+  //current_date_range.value = moment().toDate()
+  exceedCompanyFlag.value = false
+  cleanFlag.value = false
+  queryParams.value = {
+    exceed_company_flag: false,
+    clean_flag: false,
+  }
+}
+function exported() {
+  let str = ""
+  for (const key in queryParams.value) {
+    str += `${key}=${queryParams.value[key]}&`
+  }
+  //console.log("/api/sys/overStandardAnalysis/exportAnalysis?" + str)
+  window.open("/api/sys/overStandardAnalysis/exportAnalysis?" + str)
+}
+function initHead() {
+  Rs.get("sys/realTimeData/getRealTimeTitle", {}).then((res) => {
+    if (res.data) {
+      headData.value = res.data
+    }
+  })
+}
+const changeHead = (type: number) => {
+  if (type == 0) {
+    exceedCompanyFlag.value = true
+    cleanFlag.value = false
+  } else if (type == 1) {
+    exceedCompanyFlag.value = false
+    cleanFlag.value = true
+  } else {
+    return
+  }
+  headType.value = type
+
+  query()
+}
+const jump = function (v: any) {
+  console.log("jump", v)
+  router.push({
+    path: "/goLineData/oilFumeConcentration",
+    query: {
+      comName: v.company_name,
+      company_id: v.company_id,
+      backNeed: 1,
+    },
+  })
+}
+
+const chartTheme = {
+  grid: {
+    top: 40,
+    left: 10,
+    right: 20,
+    bottom: 35,
+  },
+  legend: {
+    right: 10,
+    top: 5,
+  },
+  title: {
+    left: "center",
+    top: "auto",
+    bottom: 0,
+  },
+}
+const detailInfo = ref<any>({})
+const detailDeviceInfo = ref<any>({})
+const chartData1 = ref<any>({})
+const chartData2 = ref<any>({})
+const detail = function (row: any) {
+  detailInfo.value = row
+  console.log("DETAIL", row, detailInfo.value)
+  Rs.post("sys/realTimeData/getRealTimeDetailInfo", {
+    data: {
+      device_id: row.device_id,
+    },
+  }).then((res) => {
+    detailDeviceInfo.value = res.data
+  })
+  setTimeout(() => {
+    Rs.post("sys/realTimeData/getRealTimeDetailDensityline", {
+      data: {
+        device_id: detailInfo.value.device_id,
+      },
+    }).then((res) => {
+      chartData1.value = res.data
+    })
+  }, 100)
+  setTimeout(() => {
+    Rs.post("sys/realTimeData/getRealTimeDetailPowerline", {
+      data: {
+        device_id: detailInfo.value.device_id,
+      },
+    }).then((res) => {
+      chartData2.value = res.data
+    })
+  }, 100)
+}
+
+onMounted(() => {
+  initHead()
+})
 </script>
 
 <template>
-  <div>timeData</div>
+  <div class="d-flex">
+    <div
+      class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-center border-0 h-md-50 me-5"
+      style="min-width: 250px"
+      :style="`${headType == 0 ? `background: #4c88cf` : `background: #5facd0`}`"
+      @click="changeHead(0)"
+    >
+      <div class="card-header py-5">
+        <div class="card-title d-flex">
+          <span class="text-white pt-1 fw-semibold fs-2">
+            <i class="fas fa-smog me-8 fs-1 text-white"></i>
+            排放超标
+          </span>
+          <span class="fs-2hx fw-bold text-white ms-5 lh-1 ls-n2">
+            {{ headData.exceed_company_num }}
+            <span class="fs-6 opacity-75 ms-3">家</span>
+          </span>
+        </div>
+      </div>
+    </div>
+    <div
+      class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-center border-0 h-md-50 me-5"
+      style="min-width: 250px"
+      :style="`${headType == 1 ? `background: #4c88cf` : `background: #5facd0`}`"
+      @click="changeHead(1)"
+    >
+      <div class="card-header py-5">
+        <div class="card-title d-flex">
+          <span class="text-white pt-1 fw-semibold fs-2">
+            <i class="fas fa-shower me-8 fs-1 text-white"></i>
+            净化效能低
+          </span>
+          <span class="fs-2hx fw-bold text-white ms-5 lh-1 ls-n2">
+            {{ headData.clean_low_num }}
+            <span class="fs-6 opacity-75 ms-3">台</span>
+          </span>
+        </div>
+      </div>
+    </div>
+  </div>
+  <el-form class="my-5 align-items-center" :inline="true">
+    <el-form-item class="mb-0 me-5 align-items-center" label="商户名称">
+      <el-input class="" style="width: 180px" v-model="companyName" placeholder="请输入商户名称" :size="size" />
+    </el-form-item>
+    <el-form-item class="mb-0 me-5 align-items-center" label="区域">
+      <DySelectTree
+        v-model:value="orgId"
+        style="width: 180px"
+        class="full-input"
+        :url="'sys/dict/getOrgList?type=1'"
+        :defaultExpandLevel="1"
+        :option-map="{ id: 'value', label: 'label', children: 'children' }"
+        placeholder="请选择区域..."
+      />
+    </el-form-item>
+    <el-form-item class="mb-0 me-5 align-items-center" label="菜系">
+      <DySelect
+        v-model="cateringStyle"
+        :formatRemoteData="(v:any)=>{return v?.list}"
+        :url="'sys/dict/getList?code=000010001&key=temp'"
+        :style="dySearchSelectStyle"
+        placeholder="请选择菜系"
+      ></DySelect>
+    </el-form-item>
+    <el-form-item class="mb-0 me-5 align-items-center" label="营业规模">
+      <DySelect
+        v-model="cateringScale"
+        :formatRemoteData="(v:any)=>{return v?.list}"
+        :url="'sys/dict/getList?code=000090001&key=temp'"
+        :style="dySearchSelectStyle"
+        placeholder="请选择营业规模"
+      ></DySelect>
+    </el-form-item>
+    <el-form-item class="mb-0 me-5 align-items-center" label="类型">
+      <DySelect
+        v-model="type"
+        :formatRemoteData="(v:any)=>{return v?.list}"
+        :url="'sys/dict/getList?code=000200000&key=temp'"
+        :style="dySearchSelectStyle"
+        placeholder="请选择类型"
+      ></DySelect>
+    </el-form-item>
+    <el-form-item class="mb-0 me-5 align-items-center" label="营业状态">
+      <el-select v-model="operateStatus" style="width: 150px" placeholder="请选择营业状态" :clearable="true">
+        <el-option value="0" label="营业"></el-option>
+        <el-option value="1" label="间休"></el-option>
+        <el-option value="2" label="停业"></el-option>
+      </el-select>
+    </el-form-item>
+    <el-form-item class="mb-0 me-0 align-items-center">
+      <el-button class="ms-3 mt-0 btn btn-sm btn-primary" @click="query">查询</el-button>
+      <el-button class="ms-3 mt-0 btn btn-sm btn-light-primary btn-outline" @click="reset">重置</el-button>
+      <el-button class="ms-3 mt-0 btn btn-sm btn-light-info btn-outline" @click="exported">导出</el-button>
+    </el-form-item>
+  </el-form>
+  <VbDataTable
+    ref="table"
+    :header="cols"
+    url="sys/realTimeData/getRealTimeDataForPage"
+    method="post"
+    :query-parms="queryParams"
+    :has-checkbox="false"
+    :fixed-column="true"
+    :fixed-number="2"
+    :fixed-right-number="1"
+    :scroll="{ x: 2000 }"
+  >
+    <template #company_name="{ row }">
+      <span class="text-primary" @click="jump(row)" style="cursor: pointer">{{ row["company_name"] }}</span>
+    </template>
+    <template #purifier_online_state="{ row }">
+      <div v-if="row['purifier_online_state'] == 1">
+        <img style="width: 34px; height: 34px" :src="getAssetPath(`media/table/01.gif`)" />
+        运行
+      </div>
+      <div v-if="row['purifier_online_state'] == 2 || row['purifier_online_state'] == 3">
+        <img style="width: 34px; height: 34px" :src="getAssetPath(`media/table/01.png`)" />
+        未运行
+      </div>
+      <div v-if="row['purifier_online_state'] == 4">-</div>
+    </template>
+    <template #fan_online_state="{ row }">
+      <div v-if="row['fan_online_state'] == 1">
+        <img style="width: 34px; height: 34px" :src="getAssetPath(`media/table/02.gif`)" />
+        运行
+      </div>
+      <div v-if="row['fan_online_state'] == 2 || row['purifier_online_state'] == 3">
+        <img style="width: 34px; height: 34px" :src="getAssetPath(`media/table/02.png`)" />
+        未运行
+      </div>
+      <div v-if="row['fan_online_state'] == 4">-</div>
+    </template>
+    <template #action="{ row }">
+      <el-button data-bs-toggle="modal" data-bs-target="#detail_modal" @click="detail(row)">查看详情</el-button>
+    </template>
+  </VbDataTable>
+
+  <div class="modal modal-lg fade" tabindex="-1" id="detail_modal">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header py-3">
+          <h5 class="modal-title">
+            <span class="text-info fs-2 me-5">{{ detailInfo?.company_name }}</span>
+            <span class="badge badge-light-primary">{{ detailDeviceInfo.business_state }}</span>
+          </h5>
+          <div class="btn btn-icon btn-sm btn-active-light-primary ms-2" data-bs-dismiss="modal" aria-label="Close">
+            <span class="svg-icon svg-icon-2x">
+              <inline-svg :src="getAssetPath('media/icons/duotune/arrows/arr011.svg')" />
+            </span>
+          </div>
+        </div>
+        <div class="modal-body py-5">
+          <div class="fs-3 fw-bolder mb-3">商户信息</div>
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <dl class="d-flex">
+                <dt class="text">商户名称:</dt>
+                <dd class="text">{{ detailInfo.company_name }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="12">
+              <dl class="d-flex">
+                <dt class="text">负责人:</dt>
+                <dd class="text">{{ detailDeviceInfo.company_contact }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="12">
+              <dl class="d-flex">
+                <dt class="text">详细地址:</dt>
+                <dd class="text">{{ detailDeviceInfo.company_address }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="12">
+              <dl class="d-flex">
+                <dt class="text">联系电话:</dt>
+                <dd class="text">{{ detailDeviceInfo.company_contact_tel }}</dd>
+              </dl>
+            </el-col>
+          </el-row>
+          <div class="separator border-2 my-5"></div>
+          <div class="fs-3 fw-bolder mb-3">设备信息</div>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">净化设施:</dt>
+                <dd class="text">{{ detailDeviceInfo.device_name }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">处理风量:</dt>
+                <dd class="text">{{ detailDeviceInfo.device_air_volume }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">风机状态:</dt>
+                <dd class="text">{{ detailDeviceInfo.fan_online_state }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">风机电流:</dt>
+                <dd class="text">{{ detailDeviceInfo.fan_ia }}</dd>
+              </dl>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">监控仪状态:</dt>
+                <dd class="text">{{ detailDeviceInfo.device_online_state }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">净化器状态:</dt>
+                <dd class="text">{{ detailDeviceInfo.purifier_online_state }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">净化器电流:</dt>
+                <dd class="text">{{ detailDeviceInfo.purifier_ia }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">净化效能:</dt>
+                <dd class="text">{{ detailDeviceInfo.clean_efficiency }}</dd>
+              </dl>
+            </el-col>
+          </el-row>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">油烟浓度:</dt>
+                <dd class="text">{{ detailDeviceInfo.smoke_density }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">非甲烷总烃:</dt>
+                <dd class="text">{{ detailDeviceInfo.pm25 }}</dd>
+              </dl>
+            </el-col>
+            <el-col :span="6">
+              <dl class="d-flex">
+                <dt class="text">颗粒物:</dt>
+                <dd class="text">{{ detailDeviceInfo.voc_density }}</dd>
+              </dl>
+            </el-col>
+          </el-row>
+          <div class="separator border-2 my-5"></div>
+          <div class="d-flex">
+            <div class="w-50">
+              <BaseChart :data="chartData1" :options="chartTheme" type="line" h="280"></BaseChart>
+            </div>
+            <div class="w-50">
+              <BaseChart :data="chartData2" :options="chartTheme" type="line" h="280"></BaseChart>
+            </div>
+          </div>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-primary" data-bs-dismiss="modal">关闭</button>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>

+ 3 - 27
src/views/overAnalysis/overTime.vue

@@ -3,7 +3,6 @@ import VbDataTable from "@/components/Table/VbDataTable.vue"
 import { ref } from "vue"
 import moment from "moment"
 import type { Header } from "@/components/Table/table-partials/models"
-import type { DateSelectOption } from "@/components/select/DateRangeSelect.vue"
 import { useRouter } from "vue-router"
 const router = useRouter()
 
@@ -49,28 +48,6 @@ const endDate = ref(new Date())
 const dateStr = ref(``)
 const dateValue = ref<[Date, Date]>([startDate.value, endDate.value])
 const selectValue = ref(0)
-const dates: Array<DateSelectOption> = [
-  {
-    text: "当日",
-    value: [new Date(), new Date()],
-  },
-  {
-    text: "近三日",
-    value: [moment(new Date()).add(-3, "d").toDate(), new Date()],
-  },
-  {
-    text: "近一周",
-    value: [moment(new Date()).add(-7, "d").toDate(), new Date()],
-  },
-  {
-    text: "近一月",
-    value: [moment(new Date()).add(-1, "M").toDate(), new Date()],
-  },
-  {
-    text: "本月",
-    value: [moment(new Date()).startOf("month").toDate(), new Date()],
-  },
-]
 
 const queryType = ref(0)
 const timeType = ref(0)
@@ -98,7 +75,7 @@ function changeDate(v: Date[]) {
   startDate.value = v[0]
   endDate.value = v[1]
 }
-function search() {
+function query() {
   queryParams.value = {
     query_type: queryType.value,
     time_type: timeType.value,
@@ -112,7 +89,7 @@ function reset() {
   endDate.value = new Date()
   dateValue.value = [startDate.value, endDate.value]
   dateStr.value = `${moment(startDate.value).format("YYYY-MM-DD")}至${moment(endDate.value).format("YYYY-MM-DD")}`
-  search()
+  query()
 }
 function exported() {
   let str = ""
@@ -174,13 +151,12 @@ const detail = function (v: any) {
     <DateRangeSelect
       v-model:date-value="dateValue"
       v-model:select-value="selectValue"
-      :date-array="dates"
       :select-class="'mb-0 align-items-center'"
       :date-class="'mb-0 align-items-center'"
       @change="changeDate"
     />
     <el-form-item class="mb-0 align-items-center">
-      <el-button class="ms-3 mt-0 btn btn-sm btn-primary" @click="search">查询</el-button>
+      <el-button class="ms-3 mt-0 btn btn-sm btn-primary" @click="query">查询</el-button>
       <el-button class="ms-3 mt-0 btn btn-sm btn-light-primary btn-outline" @click="reset">重置</el-button>
       <el-button class="ms-3 mt-0 btn btn-sm btn-light-info btn-outline" @click="exported">导出</el-button>
     </el-form-item>