github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/views/Entities/index.vue (about)

     1  <script setup lang="tsx">
     2  import {useI18n} from '@/hooks/web/useI18n'
     3  import {Table} from '@/components/Table'
     4  import {h, onMounted, onUnmounted, reactive, ref, watch} from 'vue'
     5  import {Pagination, TableColumn} from '@/types/table'
     6  import api from "@/api/api";
     7  import {ElButton, ElCollapse, ElCollapseItem, ElMessage, ElTag} from 'element-plus'
     8  import {ApiArea, ApiEntityShort, ApiPlugin, ApiStatistics, ApiTag} from "@/api/stub";
     9  import {useForm} from "@/hooks/web/useForm";
    10  import {useRouter} from "vue-router";
    11  import {parseTime} from "@/utils";
    12  import {ContentWrap} from "@/components/ContentWrap";
    13  import {Dialog} from '@/components/Dialog'
    14  import {UUID} from "uuid-generator-ts";
    15  import stream from "@/api/stream";
    16  import {EventStateChange} from "@/api/types";
    17  import {FormSchema} from "@/types/form";
    18  import {Form} from '@/components/Form'
    19  import {useCache} from "@/hooks/web/useCache";
    20  import {JsonEditor} from "@/components/JsonEditor";
    21  import Statistics from "@/components/Statistics/Statistics.vue";
    22  
    23  const {push} = useRouter()
    24  const {register, methods} = useForm()
    25  const {t} = useI18n()
    26  const {wsCache} = useCache()
    27  
    28  interface TableObject {
    29    tableList: ApiEntityShort[]
    30    params?: any
    31    loading: boolean
    32    sort?: string
    33    query?: string
    34    plugin?: string
    35    area?: ApiArea
    36    tags?: string[]
    37  }
    38  
    39  interface Params {
    40    page?: number;
    41    limit?: number;
    42    sort?: string;
    43  }
    44  
    45  const cachePref = 'entities'
    46  const tableObject = reactive<TableObject>(
    47      {
    48        tableList: [],
    49        loading: false,
    50        sort: wsCache.get(cachePref + 'Sort') || '-createdAt',
    51        query: wsCache.get(cachePref + 'Query'),
    52        plugin: wsCache.get(cachePref + 'Plugin')?.name,
    53        area: wsCache.get(cachePref + 'Area'),
    54        tags: wsCache.get(cachePref + 'Tags')
    55      },
    56  );
    57  
    58  const columns: TableColumn[] = [
    59    {
    60      field: 'id',
    61      label: t('entities.name'),
    62      width: "190px",
    63      sortable: true
    64    },
    65    {
    66      field: 'pluginName',
    67      label: t('entities.pluginName'),
    68      width: "140px",
    69      sortable: true
    70    },
    71    // {
    72    //   field: 'tags',
    73    //   label: t('main.tags'),
    74    //   width: "150px",
    75    //   sortable: false,
    76    //   type: 'expand'
    77    // },
    78    {
    79      field: 'areaId',
    80      label: t('entities.area'),
    81      width: "100px",
    82      sortable: true,
    83      formatter: (row: ApiEntityShort) => {
    84        return h(
    85            'span',
    86            row.area?.name
    87        )
    88      }
    89    },
    90    {
    91      field: 'description',
    92      sortable: true,
    93      label: t('entities.description')
    94    },
    95    {
    96      field: 'status',
    97      label: t('entities.status'),
    98      width: "70px",
    99    },
   100    {
   101      field: 'createdAt',
   102      label: t('main.createdAt'),
   103      type: 'time',
   104      sortable: true,
   105      width: "170px",
   106      formatter: (row: ApiEntityShort) => {
   107        return h(
   108            'span',
   109            parseTime(row.createdAt)
   110        )
   111      }
   112    },
   113    {
   114      field: 'updatedAt',
   115      label: t('main.updatedAt'),
   116      type: 'time',
   117      sortable: true,
   118      width: "170px",
   119      formatter: (row: ApiEntityShort) => {
   120        return h(
   121            'span',
   122            parseTime(row.updatedAt)
   123        )
   124      }
   125    }
   126  ]
   127  
   128  const paginationObj = ref<Pagination>({
   129    currentPage: wsCache.get(cachePref + 'CurrentPage') || 1,
   130    pageSize: wsCache.get(cachePref + 'PageSize') || 50,
   131    total: 0,
   132  })
   133  
   134  const statistic = ref<Nullable<ApiStatistics>>(null)
   135  const getStatistic = async () => {
   136  
   137    const res = await api.v1.entityServiceGetStatistic()
   138      .catch(() => {
   139      })
   140      .finally(() => {
   141  
   142      })
   143    if (res) {
   144      statistic.value = res.data
   145    }
   146  }
   147  
   148  const getList = async () => {
   149    getStatistic()
   150  
   151    tableObject.loading = true
   152  
   153    wsCache.set(cachePref + 'CurrentPage', paginationObj.value.currentPage)
   154    wsCache.set(cachePref + 'PageSize', paginationObj.value.pageSize)
   155    wsCache.set(cachePref + 'Sort', tableObject.sort)
   156    wsCache.set(cachePref + 'Query', tableObject.query)
   157    wsCache.set(cachePref + 'Tags', tableObject.tags)
   158  
   159    let params: Params = {
   160      page: paginationObj.value.currentPage,
   161      limit: paginationObj.value.pageSize,
   162      sort: tableObject.sort,
   163      query: tableObject.query || undefined,
   164      plugin: tableObject.plugin || undefined,
   165      area: tableObject?.area?.id || undefined,
   166      tags: tableObject?.tags || undefined,
   167    }
   168  
   169    const res = await api.v1.entityServiceGetEntityList(params)
   170        .catch(() => {
   171        })
   172        .finally(() => {
   173          tableObject.loading = false
   174        })
   175    if (res) {
   176      const {items, meta} = res.data;
   177      tableObject.tableList = items;
   178      paginationObj.value.currentPage = meta.pagination.page;
   179      paginationObj.value.total = meta.pagination.total;
   180    } else {
   181      tableObject.tableList = [];
   182    }
   183  }
   184  
   185  const currentID = ref('')
   186  
   187  const onStateChanged = (event: EventStateChange) => {
   188    getList()
   189  }
   190  
   191  onMounted(() => {
   192    const uuid = new UUID()
   193    currentID.value = uuid.getDashFreeUUID()
   194  
   195    setTimeout(() => {
   196      stream.subscribe('event_entity_loaded', currentID.value, onStateChanged);
   197      stream.subscribe('event_entity_unloaded', currentID.value, onStateChanged);
   198    }, 200)
   199  })
   200  
   201  onUnmounted(() => {
   202    stream.unsubscribe('event_entity_loaded', currentID.value);
   203    stream.unsubscribe('event_entity_unloaded', currentID.value);
   204  })
   205  
   206  watch(
   207      () => paginationObj.value.currentPage,
   208      () => {
   209        getList()
   210      }
   211  )
   212  
   213  watch(
   214      () => paginationObj.value.pageSize,
   215      () => {
   216        getList()
   217      }
   218  )
   219  
   220  const sortChange = (data) => {
   221    const {column, prop, order} = data;
   222    const pref: string = order === 'ascending' ? '+' : '-'
   223    tableObject.sort = pref + prop
   224    getList()
   225  }
   226  
   227  getList()
   228  
   229  const addNew = () => {
   230    push('/entities/new')
   231  }
   232  
   233  const selectRow = (row) => {
   234    if (!row) {
   235      return
   236    }
   237    const {id} = row
   238    push(`/entities/edit/${id}`)
   239  }
   240  
   241  const restart = async (entity: ApiEntityShort) => {
   242    await api.v1.developerToolsServiceReloadEntity({id: entity.id});
   243    ElMessage({
   244      title: t('Success'),
   245      message: t('message.requestSentSuccessfully'),
   246      type: 'success',
   247      duration: 2000
   248    });
   249  }
   250  
   251  const enable = async (entity: ApiEntityShort) => {
   252    if (!entity?.id) return;
   253    await api.v1.entityServiceEnabledEntity(entity.id);
   254    ElMessage({
   255      title: t('Success'),
   256      message: t('message.requestSentSuccessfully'),
   257      type: 'success',
   258      duration: 2000
   259    });
   260  }
   261  
   262  const disable = async (entity: ApiEntityShort) => {
   263    if (!entity?.id) return;
   264    await api.v1.entityServiceDisabledEntity(entity.id);
   265    ElMessage({
   266      title: t('Success'),
   267      message: t('message.requestSentSuccessfully'),
   268      type: 'success',
   269      duration: 2000
   270    });
   271  }
   272  
   273  const dialogVisible = ref(false)
   274  const importedEntity = ref(null)
   275  const showImportDialog = () => {
   276    dialogVisible.value = true
   277  }
   278  
   279  const importHandler = (val: any) => {
   280    if (importedEntity.value == val) {
   281      return
   282    }
   283    importedEntity.value = val
   284  }
   285  
   286  const importEntity = async () => {
   287    let val: ApiEntityShort;
   288    try {
   289      if (importedEntity.value?.json) {
   290        val = importedEntity.value.json as ApiEntityShort;
   291      } else if (importedEntity.value.text) {
   292        val = JSON.parse(importedEntity.value.text) as ApiEntityShort;
   293      }
   294    } catch {
   295      ElMessage({
   296        title: t('Error'),
   297        message: t('message.corruptedJsonFormat'),
   298        type: 'error',
   299        duration: 2000
   300      });
   301      return
   302    }
   303    const entity: ApiEntityShort = {
   304      id: val.id,
   305      pluginName: val.pluginName,
   306      description: val.description,
   307      area: val.area,
   308      icon: val.icon,
   309      image: val.image,
   310      autoLoad: val.autoLoad,
   311      parent: val.parent || undefined,
   312      actions: val.actions,
   313      states: val.states,
   314      attributes: val.attributes,
   315      settings: val.settings,
   316      scripts: val.scripts,
   317      tags: val.tags
   318    }
   319    const res = await api.v1.entityServiceImportEntity(entity)
   320    if (res) {
   321      ElMessage({
   322        title: t('Success'),
   323        message: t('message.importedSuccessful'),
   324        type: 'success',
   325        duration: 2000
   326      })
   327      getList()
   328    }
   329  }
   330  
   331  // search form
   332  const schema = reactive<FormSchema[]>([
   333    {
   334      field: 'name',
   335      label: t('entities.name'),
   336      component: 'Input',
   337      colProps: {
   338        span: 12
   339      },
   340      componentProps: {
   341        placeholder: t('entities.name'),
   342        onChange: (val: string) => {
   343          tableObject.query = val || undefined
   344          getList()
   345        }
   346      }
   347    },
   348    {
   349      field: 'plugin',
   350      label: t('entities.pluginName'),
   351      component: 'Plugin',
   352      value: null,
   353      colProps: {
   354        span: 12
   355      },
   356      hidden: false,
   357      componentProps: {
   358        placeholder: t('entities.pluginName'),
   359        onChange: (val: ApiPlugin) => {
   360          tableObject.plugin = val?.name || undefined
   361          wsCache.set(cachePref + 'Plugin', val)
   362          getList()
   363        }
   364      },
   365    },
   366    {
   367      field: 'area',
   368      label: t('entities.area'),
   369      value: null,
   370      component: 'Area',
   371      colProps: {
   372        span: 12
   373      },
   374      componentProps: {
   375        placeholder: t('entities.area'),
   376        onChange: (val: ApiArea) => {
   377          wsCache.set(cachePref + 'Area', val)
   378          tableObject.area = val || undefined
   379          getList()
   380        }
   381      },
   382    },
   383    {
   384      field: 'tags',
   385      label: t('main.tags'),
   386      component: 'Tags',
   387      colProps: {
   388        span: 12
   389      },
   390      value: [],
   391      hidden: false,
   392      componentProps: {
   393        placeholder: t('main.tags'),
   394        onChange: (val: ApiTag) => {
   395          wsCache.set(cachePref + 'Tags', val)
   396          tableObject.tags = val || undefined
   397          getList()
   398        }
   399      }
   400    },
   401  ])
   402  
   403  const filterList = () => {
   404    let list = ''
   405    if (tableObject?.query) {
   406      list += 'name(' + tableObject.query + ') '
   407    }
   408    if (tableObject?.plugin) {
   409      list += 'plugin(' + tableObject.plugin + ') '
   410    }
   411    if (tableObject?.area) {
   412      list += 'area(' + tableObject.area.name + ') '
   413    }
   414    if (tableObject?.tags && tableObject?.tags.length) {
   415      list += 'tags(' + tableObject.tags + ') '
   416    }
   417    if (list != '') {
   418      list = ': ' + list
   419    }
   420    return list
   421  }
   422  
   423  const {setValues, setSchema} = methods
   424  if (wsCache.get(cachePref + 'Query')) {
   425    setValues({
   426      name: wsCache.get(cachePref + 'Query')
   427    })
   428  }
   429  if (wsCache.get(cachePref + 'Plugin')) {
   430    setValues({
   431      plugin: wsCache.get(cachePref + 'Plugin')
   432    })
   433  }
   434  if (wsCache.get(cachePref + 'Area')) {
   435    setValues({
   436      area: wsCache.get(cachePref + 'Area')
   437    })
   438  }
   439  if (wsCache.get(cachePref + 'Tags')) {
   440    setValues({
   441      tags: wsCache.get(cachePref + 'Tags')
   442    })
   443  }
   444  
   445  </script>
   446  
   447  <template>
   448    <Statistics v-model="statistic" :cols="6" />
   449  
   450    <ContentWrap>
   451      <ElButton class="flex mb-20px items-left" type="primary" @click="addNew()" plain>
   452        <Icon icon="ep:plus" class="mr-5px"/>
   453        {{ t('entities.addNew') }}
   454      </ElButton>
   455      <ElButton class="flex mb-20px items-left" type="primary" @click="showImportDialog()" plain>
   456        {{ t('main.import') }}
   457      </ElButton>
   458      <ElCollapse class="mb-20px">
   459        <ElCollapseItem :title="$t('main.filter') + filterList()">
   460          <Form
   461              class="filter-form"
   462              :schema="schema"
   463              label-position="top"
   464              label-width="auto"
   465              hide-required-asterisk
   466              @register="register"
   467          />
   468        </ElCollapseItem>
   469      </ElCollapse>
   470      <Table
   471          class="entity-table"
   472          :expand="true"
   473          :selection="false"
   474          v-model:pageSize="paginationObj.pageSize"
   475          v-model:currentPage="paginationObj.currentPage"
   476          :showUpPagination="20"
   477          :columns="columns"
   478          :data="tableObject.tableList"
   479          :loading="tableObject.loading"
   480          :pagination="paginationObj"
   481          @sort-change="sortChange"
   482          style="width: 100%"
   483          @current-change="selectRow"
   484      >
   485  
   486        <template #status="{ row }">
   487          <div class="w-[100%] text-center">
   488            <ElButton :link="true" @click.prevent.stop="enable(row)" v-if="!row?.isLoaded">
   489              <Icon icon="noto:red-circle" class="mr-5px"/>
   490            </ElButton>
   491            <ElButton :link="true" @click.prevent.stop="disable(row)" v-if="row?.isLoaded">
   492              <Icon icon="noto:green-circle" class="mr-5px"/>
   493            </ElButton>
   494          </div>
   495        </template>
   496  
   497        <template #id="{row}">
   498          {{ row.id.split('.')[1] }}
   499        </template>
   500  
   501        <template #pluginName="{row}">
   502          <ElTag>
   503            {{ row.pluginName }}
   504          </ElTag>
   505        </template>
   506  
   507        <template #expand="{row}">
   508          <div class="tag-list" v-if="row.tags">
   509            <ElTag v-for="tag in row.tags" type="info" :key="tag" round effect="light" size="small">
   510              {{ tag }}
   511            </ElTag>
   512          </div>
   513        </template>
   514  
   515      </Table>
   516    </ContentWrap>
   517  
   518    <!-- import dialog -->
   519    <Dialog v-model="dialogVisible" :title="t('entities.dialogImportTitle')" :maxHeight="400" width="80%" custom-class>
   520      <JsonEditor @change="importHandler"/>
   521      <template #footer>
   522        <ElButton type="primary" @click="importEntity()" plain>{{ t('main.import') }}</ElButton>
   523        <ElButton @click="dialogVisible = false">{{ t('main.closeDialog') }}</ElButton>
   524      </template>
   525    </Dialog>
   526    <!-- /import dialog -->
   527  
   528  </template>
   529  
   530  <style lang="less">
   531  
   532  //:deep(.filter-form .el-col) {
   533  //  padding-left: 0!important;
   534  //  padding-right: 0!important;
   535  //}
   536  
   537  .entity-table {
   538    .tag-list {
   539      .el-tag {
   540        margin: 0 5px;
   541      }
   542    }
   543  
   544    :deep(.el-table__row) {
   545      cursor: pointer;
   546    }
   547  
   548    tr.el-table__row [class*="el-table__cell"] {
   549    //background-color: green; border-top: var(--el-table-border); border-bottom: none !important;
   550      border-top: var(--el-table-border);
   551    }
   552  
   553    .el-table__expanded-cell {
   554      &.el-table__cell [class*="tag-list"] {
   555      //background-color: red!important; border-bottom: none !important;
   556      }
   557  
   558      &.el-table__cell:not(:has(.tag-list)) {
   559        display: none !important;
   560      //background-color: blue!important;
   561      }
   562    }
   563  
   564    .el-table td.el-table__cell,
   565    .el-table th.el-table__cell.is-leaf {
   566      border-bottom: none !important;
   567    }
   568  
   569    .el-table--enable-row-hover .el-table__body tr.el-table__row:hover,
   570    .el-table--enable-row-hover .el-table__body tr.el-table__row:hover + tr {
   571      & > td.el-table__cell {
   572        background-color: var(--el-table-row-hover-bg-color);
   573      }
   574    }
   575  }
   576  
   577  
   578  </style>