github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/static_source/admin/src/views/Dashboard/editor/TabCardItem.vue (about) 1 <script setup lang="ts"> 2 import {computed, onMounted, onUnmounted, PropType, ref, watch} from 'vue' 3 import {useI18n} from '@/hooks/web/useI18n' 4 import {Card, CardItem, Core, eventBus, requestCurrentState} from "@/views/Dashboard/core"; 5 import {CardEditorName, CardItemList} from "@/views/Dashboard/card_items"; 6 import {JsonViewer} from "@/components/JsonViewer"; 7 import { 8 ElButton, 9 ElCascader, 10 ElCol, 11 ElCollapse, 12 ElCollapseItem, 13 ElDivider, 14 ElEmpty, 15 ElForm, 16 ElFormItem, 17 ElInput, 18 ElMessage, 19 ElPopconfirm, 20 ElRow, 21 } from 'element-plus' 22 import {JsonEditor} from "@/components/JsonEditor"; 23 import {Dialog} from "@/components/Dialog"; 24 import {ApiDashboardCardItem} from "@/api/stub"; 25 26 const {t} = useI18n() 27 28 const cardItem = ref<CardItem>(null) 29 // const card = ref<Card>({} as Card) 30 const itemTypes = CardItemList; 31 const itemProps = { 32 expandTrigger: 'hover' as const, 33 } 34 const cardItemType = computed(() => cardItem.value?.type) 35 const handleTypeChanged = (value: string[]) => { 36 cardItem.value.type = value[value.length - 1] 37 } 38 39 const props = defineProps({ 40 core: { 41 type: Object as PropType<Core>, 42 }, 43 card: { 44 type: Object as PropType<Nullable<Card>>, 45 default: () => null 46 }, 47 }) 48 49 const activeCard = computed({ 50 get(): Card { 51 return props.card as Card 52 }, 53 set(val: Card) { 54 } 55 }) 56 57 const currentCore = computed({ 58 get(): Core { 59 return props.core as Core 60 }, 61 set(val: Core) { 62 } 63 }) 64 65 watch( 66 () => props.card, 67 (val?: Card) => { 68 if (!val) return 69 activeCard.value = val 70 if (val?.selectedItem > -1) { 71 cardItem.value = val?.items[val?.selectedItem] || null 72 } else { 73 cardItem.value = null 74 } 75 76 }, 77 { 78 deep: true, 79 immediate: true 80 } 81 ) 82 83 // --------------------------------- 84 // import/export 85 // --------------------------------- 86 87 const addCardItem = () => { 88 currentCore.value.createCardItem(undefined, 'text'); 89 } 90 91 const removeCardItem = (index: number) => { 92 currentCore.value.removeCardItem(index); 93 } 94 95 const duplicate = () => { 96 activeCard.value.copyItem(activeCard.value.selectedItem); 97 } 98 99 const getCardEditorName = (name: string) => { 100 return CardEditorName(name); 101 } 102 103 const updateCardItem = async () => { 104 const {data} = await currentCore.value.updateCard(); 105 106 if (data) { 107 ElMessage({ 108 title: t('Success'), 109 message: t('message.updatedSuccessfully'), 110 type: 'success', 111 duration: 2000 112 }); 113 } 114 } 115 116 const updateCurrentState = () => { 117 if (cardItem.value.entityId) { 118 requestCurrentState(cardItem.value?.entityId) 119 } 120 } 121 122 const eventHandler = (event: string, args: any[]) => { 123 switch (event) { 124 case 'showCardItemImportDialog': 125 cardIdForImport.value = args 126 importDialogVisible.value = true 127 break; 128 case 'showCardItemExportDialog': 129 showExportDialog(args) 130 break; 131 } 132 } 133 const cardIdForImport = ref<number>() 134 onMounted(() => { 135 eventBus.subscribe(['showCardItemImportDialog', 'showCardItemExportDialog'], eventHandler) 136 }) 137 138 onUnmounted(() => { 139 eventBus.unsubscribe(['showCardItemImportDialog', 'showCardItemExportDialog'], eventHandler) 140 }) 141 142 // --------------------------------- 143 // import/export 144 // --------------------------------- 145 146 const dialogSource = ref({}) 147 const importDialogVisible = ref(false) 148 const exportDialogVisible = ref(false) 149 const importedCardItem = ref(null) 150 151 const prepareForExport = (cardItemId?: number) => { 152 if (currentCore.value.activeCard == undefined) { 153 return; 154 } 155 dialogSource.value = currentCore.value.serializeCardItem(cardItemId) 156 } 157 158 const showExportDialog = (cardItemId?: number) => { 159 prepareForExport(cardItemId) 160 exportDialogVisible.value = true 161 } 162 163 const importHandler = (val: any) => { 164 if (importedCardItem.value == val) { 165 return 166 } 167 importedCardItem.value = val 168 } 169 170 const importCardItem = async () => { 171 let cardItem: ApiDashboardCardItem 172 try { 173 if (importedCardItem.value?.json) { 174 cardItem = importedCardItem.value.json as ApiDashboardCardItem; 175 } else if (importedCardItem.value.text) { 176 cardItem = JSON.parse(importedCardItem.value.text) as ApiDashboardCardItem; 177 } 178 } catch { 179 ElMessage({ 180 title: t('Error'), 181 message: t('message.corruptedJsonFormat'), 182 type: 'error', 183 duration: 2000 184 }); 185 return 186 } 187 const res = await currentCore.value.importCardItem(cardIdForImport.value, cardItem); 188 if (res) { 189 cardIdForImport.value = undefined 190 ElMessage({ 191 title: t('Success'), 192 message: t('message.importedSuccessful'), 193 type: 'success', 194 duration: 2000 195 }) 196 } 197 importDialogVisible.value = false 198 } 199 200 201 </script> 202 203 <template> 204 205 <ElRow class="mb-10px" v-if="activeCard.selectedItem !== -1"> 206 <ElCol> 207 <ElDivider content-position="left">{{ $t('dashboard.cardItemOptions') }}</ElDivider> 208 </ElCol> 209 </ElRow> 210 211 <ElForm 212 v-if="cardItem" 213 :model="cardItem" 214 label-position="top" 215 style="width: 100%" 216 ref="cardItemForm" 217 > 218 219 <ElRow> 220 <ElCol> 221 222 <ElFormItem :label="$t('dashboard.editor.type')" prop="type"> 223 <ElCascader 224 v-model="cardItemType" 225 :options="itemTypes" 226 :props="itemProps" 227 :placeholder="$t('dashboard.editor.pleaseSelectType')" 228 style="width: 100%" 229 @change="handleTypeChanged" 230 /> 231 </ElFormItem> 232 </ElCol> 233 </ElRow> 234 235 <ElRow> 236 <ElCol> 237 <ElFormItem :label="$t('dashboard.editor.title')" prop="title"> 238 <ElInput v-model="cardItem.title"/> 239 </ElFormItem> 240 </ElCol> 241 242 </ElRow> 243 244 <component 245 :is="getCardEditorName(cardItem.type)" 246 :core="core" 247 :item="cardItem" 248 /> 249 </ElForm> 250 251 <ElEmpty v-if="!activeCard.items.length || activeCard.selectedItem === -1" :rows="5" class="mt-20px mb-20px" 252 description="Select card item or"> 253 <ElButton type="primary" @click="addCardItem()"> 254 {{ t('dashboard.editor.addNewCardItem') }} 255 </ElButton> 256 <ElButton type="primary" @click="importDialogVisible = true"> 257 {{ t('main.import') }} 258 </ElButton> 259 </ElEmpty> 260 261 <ElRow class="mb-10px mt-10px" v-if="activeCard.selectedItem > -1 && cardItem.entity"> 262 <ElCol> 263 <ElCollapse> 264 <ElCollapseItem :title="$t('dashboard.editor.eventstateJSONobject')"> 265 <ElButton class="mb-10px w-[100%]" @click.prevent.stop="updateCurrentState()"> 266 <Icon icon="ep:refresh" class="mr-5px"/> 267 {{ $t('dashboard.editor.getEvent') }} 268 </ElButton> 269 <JsonViewer v-model="cardItem.lastEvent"/> 270 </ElCollapseItem> 271 </ElCollapse> 272 </ElCol> 273 </ElRow> 274 275 <ElRow v-if="activeCard.selectedItem > -1" class="mb-10px"> 276 <ElCol> 277 <ElDivider class="mb-10px" content-position="left">{{ $t('main.actions') }}</ElDivider> 278 </ElCol> 279 </ElRow> 280 281 <div v-if="activeCard.selectedItem > -1" class="text-right"> 282 283 <ElButton type="primary" @click.prevent.stop="updateCardItem" plain>{{ 284 $t('main.update') 285 }} 286 </ElButton> 287 288 <ElButton @click.prevent.stop="duplicate">{{ $t('main.duplicate') }}</ElButton> 289 290 <ElPopconfirm 291 :confirm-button-text="$t('main.ok')" 292 :cancel-button-text="$t('main.no')" 293 width="250" 294 style="margin-left: 10px;" 295 :title="$t('main.are_you_sure_to_do_want_this?')" 296 @confirm="removeCardItem(activeCard.selectedItem)" 297 > 298 <template #reference> 299 <ElButton type="danger" plain> 300 <Icon icon="ep:delete" class="mr-5px"/> 301 {{ t('main.remove') }} 302 </ElButton> 303 </template> 304 </ElPopconfirm> 305 </div> 306 307 <!-- export dialog --> 308 <Dialog v-model="exportDialogVisible" :title="t('main.dialogExportTitle')" :maxHeight="400" width="80%"> 309 <JsonViewer v-model="dialogSource"/> 310 </Dialog> 311 <!-- /export dialog --> 312 313 <!-- import dialog --> 314 <Dialog v-model="importDialogVisible" :title="t('main.dialogImportTitle')" :maxHeight="400" width="80%" 315 custom-class> 316 <JsonEditor @change="importHandler"/> 317 <template #footer> 318 <ElButton type="primary" @click="importCardItem()" plain>{{ t('main.import') }}</ElButton> 319 <ElButton @click="importDialogVisible = false">{{ t('main.closeDialog') }}</ElButton> 320 </template> 321 </Dialog> 322 <!-- /import dialog --> 323 324 </template> 325 326 <style lang="less"> 327 328 </style>