github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/views/Dashboard/index.vue (about) 1 <script setup lang="ts"> 2 import {useI18n} from '@/hooks/web/useI18n' 3 import {Table} from '@/components/Table' 4 import {h, reactive, ref, watch} from 'vue' 5 import {Pagination, TableColumn} from '@/types/table' 6 import api from "@/api/api"; 7 import {ElButton, ElMessage} from 'element-plus' 8 import {ApiDashboard} from "@/api/stub"; 9 import {useRouter} from "vue-router"; 10 import {parseTime} from "@/utils"; 11 import {ContentWrap} from "@/components/ContentWrap"; 12 import {Dialog} from '@/components/Dialog' 13 import {Core} from "@/views/Dashboard/core"; 14 import {useCache} from "@/hooks/web/useCache"; 15 import {JsonEditor} from "@/components/JsonEditor"; 16 import {prepareUrl} from "@/utils/serverId"; 17 import {Infotip} from "@/components/Infotip"; 18 19 const {push} = useRouter() 20 const counter = ref(0); 21 const {t} = useI18n() 22 const {wsCache} = useCache() 23 24 interface TableObject { 25 tableList: ApiDashboard[] 26 params?: any 27 loading: boolean 28 sort?: string 29 } 30 31 interface Params { 32 page?: number; 33 limit?: number; 34 sort?: string; 35 } 36 37 const cachePref = 'dashboard' 38 const tableObject = reactive<TableObject>( 39 { 40 tableList: [], 41 loading: false, 42 sort: wsCache.get(cachePref + 'Sort') || '-createdAt' 43 }, 44 ); 45 46 const columns: TableColumn[] = [ 47 { 48 field: 'id', 49 label: t('dashboard.id'), 50 sortable: true, 51 width: "60px" 52 }, 53 { 54 field: 'name', 55 label: t('dashboard.name'), 56 sortable: true, 57 width: "170px" 58 }, 59 { 60 field: 'areaId', 61 label: t('dashboard.area'), 62 width: "100px", 63 sortable: true, 64 formatter: (row: ApiDashboard) => { 65 return h( 66 'span', 67 row.area?.name 68 ) 69 } 70 }, 71 { 72 field: 'description', 73 sortable: true, 74 label: t('dashboard.description') 75 }, 76 { 77 field: 'link', 78 label: t('dashboard.landing'), 79 width: "120px", 80 }, 81 { 82 field: 'operations', 83 label: t('dashboard.operations'), 84 width: "120px", 85 }, 86 { 87 field: 'createdAt', 88 label: t('main.createdAt'), 89 type: 'time', 90 sortable: true, 91 width: "170px", 92 formatter: (row: ApiDashboard) => { 93 return h( 94 'span', 95 parseTime(row.createdAt) 96 ) 97 } 98 }, 99 { 100 field: 'updatedAt', 101 label: t('main.updatedAt'), 102 type: 'time', 103 sortable: true, 104 width: "170px", 105 formatter: (row: ApiDashboard) => { 106 return h( 107 'span', 108 parseTime(row.updatedAt) 109 ) 110 } 111 } 112 ] 113 const paginationObj = ref<Pagination>({ 114 currentPage: wsCache.get(cachePref + 'CurrentPage') || 1, 115 pageSize: wsCache.get(cachePref + 'PageSize') || 50, 116 total: 0, 117 }) 118 119 const getList = async () => { 120 tableObject.loading = true 121 122 wsCache.set(cachePref + 'CurrentPage', paginationObj.value.currentPage) 123 wsCache.set(cachePref + 'PageSize', paginationObj.value.pageSize) 124 wsCache.set(cachePref + 'Sort', tableObject.sort) 125 126 let params: Params = { 127 page: paginationObj.value.currentPage, 128 limit: paginationObj.value.pageSize, 129 sort: tableObject.sort, 130 } 131 132 const res = await api.v1.dashboardServiceGetDashboardList(params) 133 .catch(() => { 134 }) 135 .finally(() => { 136 tableObject.loading = false 137 }) 138 if (res) { 139 const {items, meta} = res.data; 140 tableObject.tableList = items; 141 paginationObj.value.currentPage = meta.pagination.page; 142 paginationObj.value.total = meta.pagination.total; 143 counter.value = meta.pagination.total + 1 144 } else { 145 tableObject.tableList = []; 146 } 147 } 148 149 watch( 150 () => paginationObj.value.currentPage, 151 () => { 152 getList() 153 } 154 ) 155 156 watch( 157 () => paginationObj.value.pageSize, 158 () => { 159 getList() 160 } 161 ) 162 163 const sortChange = (data) => { 164 const {column, prop, order} = data; 165 const pref: string = order === 'ascending' ? '+' : '-' 166 tableObject.sort = pref + prop 167 getList() 168 } 169 170 getList() 171 172 const addNew = () => { 173 Core.createNew('new' + counter.value) 174 .then((dashboard: ApiDashboard) => { 175 ElMessage({ 176 title: t('Success'), 177 message: t('message.createdSuccessfully'), 178 type: 'success', 179 duration: 2000 180 }); 181 push({path: `/dashboards/edit/${dashboard.id}`}); 182 }) 183 .catch((e) => { 184 counter.value++ 185 }) 186 } 187 188 const editDashboard = (row: ApiDashboard) => { 189 push(`/dashboards/edit/${row.id}`) 190 } 191 192 const showDashboard = (row: ApiDashboard) => { 193 push(`/dashboards/view/${row.id}`) 194 } 195 196 //todo: experimental function 197 const openLanding = (item: ApiDashboard): string => { 198 const uri = window.location.origin || import.meta.env.VITE_API_BASEPATH as string; 199 const accessToken = wsCache.get("accessToken") 200 const url = prepareUrl(uri + '/#/landing/' + item.id + '?access_token=' + accessToken); 201 window.open(url, '_blank', 'noreferrer'); 202 } 203 204 const dialogVisible = ref(false) 205 const importValue = ref(null) 206 207 const importHandler = (val: any) => { 208 if (importValue.value == val) { 209 return 210 } 211 importValue.value = val 212 } 213 214 const importDashboard = async () => { 215 let dashboard: ApiDashboard 216 try { 217 if (importValue.value?.json) { 218 dashboard = importValue.value.json as ApiDashboard; 219 } else if (importValue.value.text) { 220 dashboard = JSON.parse(importValue.value.text) as ApiDashboard; 221 } 222 } catch { 223 ElMessage({ 224 title: t('Error'), 225 message: t('message.corruptedJsonFormat'), 226 type: 'error', 227 duration: 2000 228 }); 229 return 230 } 231 const data = await Core._import(dashboard); 232 if (data) { 233 await getList(); 234 ElMessage({ 235 title: t('Success'), 236 message: t('message.importedSuccessful'), 237 type: 'success', 238 duration: 2000 239 }); 240 } 241 } 242 243 </script> 244 245 <template> 246 <ContentWrap> 247 <ElButton class="flex mb-20px items-left" type="primary" @click="addNew()" plain> 248 <Icon icon="ep:plus" class="mr-5px"/> 249 {{ t('dashboard.addNewDashboard') }} 250 </ElButton> 251 <ElButton class="flex mb-20px items-left" type="primary" @click="dialogVisible = true" plain> 252 {{ t('main.import') }} 253 </ElButton> 254 255 <Infotip 256 type="warning" 257 :show-index="false" 258 title="WARNING" 259 :schema="[ 260 { 261 label: 'The functionality is experimental and may change in the future.', 262 }, 263 { 264 label: 'Added a landing page link that provides direct access to the dashboard. The direct link allows you to embed the dashboard into your solution.', 265 }, 266 { 267 label: ' ', 268 }, 269 { 270 label: 'Be careful, the link contains your access token. Generate a link on behalf of a non-privileged user.', 271 }, 272 ]" 273 /> 274 275 <Table 276 :selection="false" 277 v-model:pageSize="paginationObj.pageSize" 278 v-model:currentPage="paginationObj.currentPage" 279 :showUpPagination="20" 280 :columns="columns" 281 :data="tableObject.tableList" 282 :loading="tableObject.loading" 283 :pagination="paginationObj" 284 @sort-change="sortChange" 285 style="width: 100%" 286 @current-change="showDashboard" 287 > 288 289 <template #status="{ row }"> 290 291 <div class="w-[100%] text-center"> 292 <Icon icon="noto:green-circle" class="mr-5px" v-if="row?.isLoaded"/> 293 <Icon icon="noto:red-circle" class="mr-5px" v-if="!row?.isLoaded"/> 294 </div> 295 296 </template> 297 298 <template #link="{ row }"> 299 <div class="w-[100%] text-center landing-link"> 300 <ElButton link @click.prevent.stop="openLanding(row)"> 301 {{ $t('main.open') }} <Icon icon="gg:external"/> 302 </ElButton> 303 </div> 304 </template> 305 306 <template #operations="{ row }"> 307 <div class="w-[100%] text-center edit-link"> 308 <ElButton link @click.prevent.stop="editDashboard(row)"> 309 {{ $t('main.edit') }} 310 </ElButton> 311 </div> 312 </template> 313 </Table> 314 </ContentWrap> 315 316 <!-- import dialog --> 317 <Dialog v-model="dialogVisible" :title="t('dashboard.dialogImportTitle')" :maxHeight="400" width="80%" custom-class> 318 <JsonEditor @change="importHandler"/> 319 <template #footer> 320 <ElButton type="primary" @click="importDashboard()" plain>{{ t('main.import') }}</ElButton> 321 <ElButton @click="dialogVisible = false">{{ t('main.closeDialog') }}</ElButton> 322 </template> 323 </Dialog> 324 <!-- /import dialog --> 325 326 </template> 327 328 <style lang="less"> 329 330 .landing-link, .edit-link { 331 .el-button { 332 font-size: calc(100% - 1px); 333 } 334 335 .el-icon { 336 font-size: calc(100% - 6px) !important; 337 } 338 } 339 340 .landing-link { 341 display: none; 342 } 343 344 .el-table__row { 345 cursor: pointer; 346 347 &:hover { 348 .landing-link { 349 display: inherit; 350 351 } 352 } 353 } 354 355 </style>