System: ты программист на NUXT 3
User: это код страницы /proxy : <template> <ProxyAddForm /> <ProxyTable class="mb-8" /> </template>

это код компонента добавление новых прокси в базу через api запрос :

<script setup lang="ts"> import { ref } from "vue";

const emit = defineEmits(); // Определяем события

const proxyUrls = ref(""); const service = ref(""); const defaultType = ref(""); const isSubmitting = ref(false); const toaster = useToaster(); // Инициализируем тостер

const addProxy = async () => { isSubmitting.value = true; // Блокируем кнопку

try { const response = await $fetch("/api/proxy", { method: "POST", body: { urls: proxyUrls.value, type: defaultType.value, service: service.value, }, });

// Проверяем статус ответа от сервера
if (response.status === "success") {
  console.log("proxy added!");
  toaster.show({
    title: "Success",
    message: response.message || "Proxy added!",
    color: "success",
    icon: "ph:check",
    closable: true,
  });
  emit("proxy-added"); // Вызываем событие для обновления списка прокси
} else if (response.status === "error") {
  toaster.show({
    title: "Error",
    message: response.message || "Failed to add proxy!",
    color: "error",
    icon: "ph:mask-sad-light",
    closable: true,
  });
} else {
  throw new Error("Unexpected response status");
}
Copy
} catch (err) { console.error(err); toaster.show({ title: "Error", message: err.message || "Failed to add proxy!", color: "error", icon: "ph:skull", closable: true, }); } finally { isSubmitting.value = false; // Разблокируем кнопку } }; </script>

<template> <div class="max-w-full pb-6"> <BaseCard class="p-6 w-full"> <div class="grid grid-cols-12 gap-4"> <!-- Левая колонка --> <div class="col-span-12 sm:col-span-6 flex flex-col"> <BaseTextarea v-model="proxyUrls" label="Proxy Urls" placeholder="write your proxy urls" class="h-32" /> </div>

    <!-- Правая колонка для Service и Default Type с кнопкой -->
    <div class="col-span-12 sm:col-span-6 flex flex-col justify-between">
      <!-- Верхний контейнер для полей Service и Default Type -->
      <div class="grid gap-4 grid-cols-2 mb-4">
        <BaseInput
          v-model="service"
          label="Service"
          placeholder="proxy.com"
          type="text"
        />
        <BaseInput
          v-model="defaultType"
          label="Default Type"
          placeholder="https"
          type="text"
        />
      </div>

      <!-- Нижний контейнер для кнопки -->
      <BaseButton
        @click="addProxy"
        class="w-full"
        color="primary"
        :disabled="isSubmitting"
        :loading="isSubmitting"
      >
        Add proxy
      </BaseButton>
    </div>
  </div>
</BaseCard>
Copy
</div> </template>

<style scoped> /* Ваши стили */ </style>

это код компонента таблицы прокси: <script setup lang="ts"> import { Proxy } from "@/types/proxy"; const emit = defineEmits(); // Определяем события

const filter = ref(""); const perPage = ref(10); const page = ref(1); const isFirstRequest = ref(true); // Переменная для отслеживания первого запроса

// Глобальные переменные для данных и ошибок const dataResp = ref<{ total: number; data: Proxy[] } | null>(null); // Инициализируем как null const error = ref(""); const status = ref("idle");

// Функция для выполнения запроса const fetchData = async () => { // Устанавливаем статус в 'pending' перед выполнением запроса status.value = "pending";

try { //console.log(response); // Обновляем глобальные переменные const resp = await $fetch("/api/proxy", { method: "GET", // Устанавливаем метод GET params: { filter: filter.value, perPage: perPage.value, page: page.value, upd: !isFirstRequest.value, // Используем isFirstRequest для параметра update }, headers: { accept: 'application/json', }, }); status.value = "success";

if (resp.data !==null /*&& resp.status === "success"*/){ //success  в т.ч. и новые данные из бд при параметре update=true
  dataResp.value = resp;
} else if(resp.status === "error"){
  console.error("error:", resp.message);
  status.value = "error"; // Устанавливаем статус в 'error'
}
error.value = null; // Сбрасываем ошибку
Copy
} catch (err) { console.error("Fetch error:", err); error.value = err; // Обрабатываем ошибку status.value = "error"; // Устанавливаем статус в 'error' } };

// Выполняем первый запрос await fetchData();

// Обновление данных каждую секунду let interval: ReturnType<typeof setInterval>;

onMounted(() => { // Устанавливаем интервал для обновления данных каждую секунду interval = setInterval(() => { isFirstRequest.value = false; // Устанавливаем флаг для обновления fetchData(); // Обновляем данные с параметром update=true }, 7000); // 1000 миллисекунд = 1 секунда });

onBeforeUnmount(() => { // Очищаем интервал при размонтировании компонента clearInterval(interval); isFirstRequest.value = true; // Устанавливаем флаг });

/*const selected = ref<number[]>([]); const isAllVisibleSelected = computed(() => { return selected.value.length === dataResp?.value.data.length; });

function toggleAllVisibleSelection() { if (isAllVisibleSelected.value) { selected.value = []; } else { selected.value = dataResp?.value.data.map((item) => item.id) ?? []; } } */ const selected = ref<number[]>([]); const isAllVisibleSelected = computed(() => { return selected.value.length === (dataResp.value?.data?.length || 0); // Проверяем на наличие данных });

function toggleAllVisibleSelection() { if (isAllVisibleSelected.value) { selected.value = []; } else { selected.value = dataResp.value?.data?.map((item) => item.id) ?? []; } } const isModalOpen = ref(false); const selectedProxy = ref<Proxy[]>([]); // Пустой массив прокси по умолчанию

function openModal(proxy: Proxy) { selectedProxy.value = proxy; isModalOpen.value = true; }

function getStatus(item) { if (item.disabledAt) { return "disabled"; } else if (item.lockedAt) { return "locked"; } else { return "free"; } }

// Пример добавления нового прокси в массив /*const addProxy = (newProxy: Proxy) => { selectedProxy.value.push(newProxy); };

// Пример использования const exampleProxy: Proxy = { id: 1, url: 'https://1.1.1.1@usr:1234', lastUseAt: new Date().toISOString(), successCount: 0, failCount: 0, type: 'https', service: 'socks5.com', lockedAt: new Date().toISOString(), createdAt: new Date().toISOString(), disabledAt: '', };

// Добавляем новый прокси в массив addProxy(exampleProxy);*/

// Обработчик для обновления данных const updateData = () => { fetchData(); // Вызываем fetchData для обновления данных };

// Слушаем событие от ProxyAddForm emit('update-data', updateData); </script>

<template> <div> <TairoContentWrapper> <template v-if="dataResp?.total >= perPage" #left> <BaseInput v-model="filter" icon="lucide:search" placeholder="Filter users..." :classes="{ wrapper: 'w-full sm:w-auto', }" /> </template> <template v-if="dataResp?.total >= perPage" #right> <BaseSelect v-model="perPage" label="" :classes="{ wrapper: 'w-full sm:w-40', }" > <option :value="10">10 per page</option> <option :value="25">25 per page</option> </BaseSelect> </template> <div> <div v-if="status !== 'pending' && dataResp?.data?.length === 0"> <BasePlaceholderPage title="Нет совпадающих результатов" subtitle="Похоже, мы не смогли найти результаты, соответствующие вашим запросам. Попробуйте использовать другие поисковые запросы." > <template #image> <img class="hidden dark:block" src="/img/illustrations/placeholders/flat/placeholder-search-4-dark.svg" alt="Placeholder image" /> </template> </BasePlaceholderPage> </div> <div v-else> <div class="w-full"> <TairoTable shape="rounded" :scrollable="false"> <template #header> <TairoTableHeading uppercase spaced class="p-4"> <div class="flex items-center"> <BaseCheckbox :model-value="isAllVisibleSelected" :indeterminate=" selected.length > 0 && !isAllVisibleSelected " name="table-1-main" shape="rounded" class="text-primary-500" @click="toggleAllVisibleSelection" /> </div> </TairoTableHeading> <TairoTableHeading uppercase spaced> Id </TairoTableHeading> <TairoTableHeading uppercase spaced> Url </TairoTableHeading> <TairoTableHeading uppercase spaced> Stat </TairoTableHeading> <TairoTableHeading uppercase spaced> Service </TairoTableHeading> <TairoTableHeading uppercase spaced> Status </TairoTableHeading> <TairoTableHeading uppercase spaced> Action </TairoTableHeading> </template>

          <TairoTableRow v-if="selected.length > 0" :hoverable="false">
            <TairoTableCell
              colspan="10"
              class="bg-success-100 text-success-700 dark:bg-success-700 dark:text-success-100 p-4"
            >
              You have selected {{ selected.length }} items of the total
              {{ dataResp?.total }} items.
              <a
                href="#"
                class="outline-none hover:underline focus:underline"
                >Click here to everything</a
              >
            </TairoTableCell>
          </TairoTableRow>
          <TairoTableRow
Copy
<!-- v-if="dataResp?.data != null"--> v-for="item in dataResp?.data" :key="item.id" > <TairoTableCell spaced> <div class="flex items-center"> <BaseCheckbox v-model="selected" :value="item.id" :name="item-checkbox-${item.id}" shape="rounded" class="text-primary-500" /> </div> </TairoTableCell> <TairoTableCell spaced> <div class="flex items-center"> <div class="ms-3 leading-none"> <h4 class="font-sans text-sm font-medium"> Id: {{ item.id }} </h4> <p class="text-muted-400 font-sans text-xs"> {{ item.createdAt }} </p> </div> </div> </TairoTableCell> <TairoTableCell spaced> <div class="flex items-center"> <div class="ms-3 leading-none"> <h4 class="font-sans text-sm font-medium"> {{ item.url }} </h4> <p class="text-muted-400 font-sans text-xs"> Type: {{ item.typeProxy }} </p> </div> </div> </TairoTableCell> <TairoTableCell light spaced> <div class="ms-3 leading-none"> <h4 class="font-sans text-sm font-medium"> Success: {{ item.successCount }} </h4> <p class="text-muted-400 font-sans text-xs"> Fail: {{ item.failCount }} </p> </div> </TairoTableCell> <TairoTableCell light spaced> <div class="ms-3 leading-none"> {{ item.service }} </div> </TairoTableCell> <TairoTableCell spaced class="capitalize"> <template v-if="getStatus(item) === 'free'"> <BaseTag color="success" flavor="pastel" shape="full" size="sm" class="font-medium" > Free </BaseTag> <p class="text-muted-400 font-sans text-xs"> Last: {{ item.lastUseAt }} </p> </template> <template v-else-if="getStatus(item) === 'locked'"> <BaseTag color="info" flavor="pastel" shape="full" size="sm" class="font-medium" > Locked </BaseTag> <p class="text-muted-400 font-sans text-xs"> Locked: {{ item.lockedAt }} </p> </template> <template v-else-if="getStatus(item) === 'disabled'"> <BaseTag color="dark" flavor="pastel" shape="full" size="sm" class="font-medium" > Disabled </BaseTag> <p class="text-muted-400 font-sans text-xs"> Disabled: {{ item.disabled }} </p> </template> </TairoTableCell> <TairoTableCell spaced> <div class="flex justify-end"> <BaseButtonAction @click="openModal(item)" color="muted"> <span>View</span> </BaseButtonAction> </div> </TairoTableCell> </TairoTableRow> </TairoTable> <ProxyModal v-model:isOpen="isModalOpen" :proxy="selectedProxy" /> </div> <div v-if="dataResp?.total >= perPage" class="mt-6"> <BasePagination noRouter :total-items="dataResp?.total ?? 0" :item-per-page="perPage" :current-page="page" @update:current-page="updatePage" shape="curved" /> </div> </div> </div> </TairoContentWrapper> </div> </template>Copy

задача - нужно доделать весь этот пример: сделать так чтобы при добавление новых прокси через форму, данные в таблице обновились, т.е нужно сделать связь реактивную между компонентами через родительскую страницу
Assistant: