loading status

This commit is contained in:
Kseninia Mikhaylova 2025-06-25 12:11:55 +03:00
parent d9befa634a
commit 9c025628ff
3 changed files with 133 additions and 72 deletions

View File

@ -4,26 +4,33 @@ const availableActions = ref<Record<string, { label: string; allowedTypes: numbe
export function useKompasActions() { export function useKompasActions() {
const { sendCommandToPython, isReady } = usePythonBridge() const { sendCommandToPython, isReady } = usePythonBridge()
const loading = ref(false)
const error = ref<string | null>(null) const error = ref<string | null>(null)
/** /**
* Загружает список действий с бэкенда * Загружает список действий с бэкенда
*/ */
async function loadActions() { async function loadActions() {
loading.value = true if (!isReady.value) {
console.warn('Bridge не готов')
return
}
error.value = null error.value = null
try { try {
const actions = await sendCommandToPython<{ // Отправляем команду и ожидаем ответ в новом формате
[key: string]: { label: string; allowed_types: number[] } const response = await sendCommandToPython<{
status: 'success' | 'error'
data: Record<string, { label: string; allowed_types: number[] }> | null
error: string | null
}>('get_available_actions') }>('get_available_actions')
if (actions) { if (response.status === 'success' && response.data) {
// Преобразуем все ключи `allowed_types` → `allowedTypes` // Преобразуем данные
const convertedActions: Record<string, { label: string; allowedTypes: number[] }> = {} const convertedActions: Record<string, { label: string; allowedTypes: number[] }> = {}
for (const key in actions) { for (const key in response.data) {
const { label, allowed_types } = actions[key] const { label, allowed_types } = response.data[key]
convertedActions[key] = { convertedActions[key] = {
label, label,
allowedTypes: allowed_types allowedTypes: allowed_types
@ -31,18 +38,18 @@ export function useKompasActions() {
} }
availableActions.value = convertedActions availableActions.value = convertedActions
} else {
throw new Error(response.error || 'Неизвестная ошибка')
} }
} catch (err: any) { } catch (err: any) {
console.error('Ошибка при получении действий:', err) console.error('Ошибка при получении действий:', err)
error.value = 'Не удалось загрузить список действий' error.value = 'Не удалось загрузить список действий'
} finally {
loading.value = false
} }
} }
return { return {
availableActions, availableActions,
loading,
error, error,
loadActions, loadActions,
} }

View File

@ -1,4 +1,4 @@
import { ref, onMounted } from 'vue' // composables/usePythonBridge.ts
declare global { declare global {
interface Window { interface Window {
@ -12,29 +12,25 @@ declare global {
new(transport: any, ready: (channel: any) => void): void new(transport: any, ready: (channel: any) => void): void
} }
} }
interface PythonEvent {
type: string
data: any
}
const pyjs = ref<any>(null)
const isReady = ref<boolean>(false)
// Хранилище обработчиков событий
const pythonEventHandlers = new Map<string, Array<(data: any) => void>>()
function receiveFromPython(eventType: string, data: any) {
console.warn("🟢 Event from Python:", eventType, data)
const handlers = pythonEventHandlers.get(eventType) || []
handlers.forEach(handler => handler(data))
}
export function usePythonBridge() { export function usePythonBridge() {
const pyjs = useState<any>('bridge_pyjs', () => null)
const isReady = useState<boolean>('bridge_ready', () => false)
const isAvailableOnPlatform = useState<boolean>('bridge_av', () => false)
const loading = useState<boolean>('bridge_loading', () => false)
// Хранилище обработчиков событий
const pythonEventHandlers = new Map<string, Array<(data: any) => void>>()
function receiveFromPython(eventType: string, data: any) {
console.warn("🟢 Event from Python:", eventType, data)
const handlers = pythonEventHandlers.get(eventType) || []
handlers.forEach(handler => handler(data))
}
function setupQWebChannel() { function setupQWebChannel() {
if (window.QWebChannel && window.qt && window.qt.webChannelTransport) { if (window.QWebChannel && window.qt && window.qt.webChannelTransport) {
new window.QWebChannel(window.qt.webChannelTransport, function (channel) { new window.QWebChannel(window.qt.webChannelTransport, function (channel) {
isAvailableOnPlatform.value = true
isReady.value = true isReady.value = true
pyjs.value = channel.objects.pyjs pyjs.value = channel.objects.pyjs
@ -47,6 +43,7 @@ export function usePythonBridge() {
} }
}) })
} else { } else {
isAvailableOnPlatform.value = false
console.error('Qt WebChannel недоступен') console.error('Qt WebChannel недоступен')
} }
} }
@ -55,24 +52,34 @@ export function usePythonBridge() {
command: string, command: string,
data: Record<string, any> = {} data: Record<string, any> = {}
): Promise<T> { ): Promise<T> {
return new Promise((resolve, reject) => { if (!isReady.value) {
if (!isReady.value) { throw new Error('Мост еще не готов')
reject(new Error('Мост еще не готов')) }
return
}
if (!pyjs.value || !pyjs.value.callFromJS) { if (!pyjs.value || !pyjs.value.callFromJS) {
reject(new Error('Python недоступен')) throw new Error('Python недоступен')
return }
}
pyjs.value.callFromJS(command, JSON.stringify(data), (result: string) => { loading.value = true
return new Promise<T>((resolve, reject) => {
setTimeout(() => {
try { try {
resolve(JSON.parse(result)) pyjs.value.callFromJS(command, JSON.stringify(data), (result: string) => {
} catch (e) { try {
reject(e) const parsed = JSON.parse(result)
resolve(parsed)
} catch (e) {
reject(e)
} finally {
loading.value = false
}
})
} catch (err) {
reject(err)
loading.value = false
} }
}) }, 300)
}) })
} }
@ -96,7 +103,7 @@ export function usePythonBridge() {
} }
const checkBridge = () => { const checkBridge = () => {
if (window.QWebChannel && window.qt?.webChannelTransport) { if (import.meta.client && window.QWebChannel && window.qt?.webChannelTransport) {
setupQWebChannel() setupQWebChannel()
} }
@ -107,12 +114,16 @@ export function usePythonBridge() {
} }
} }
onMounted(() => { // Вызываем один раз при первом монтировании
if (!isReady.value) {
checkBridge() checkBridge()
}) }
return { return {
pyjs,
isReady, isReady,
isAvailableOnPlatform,
loading,
sendCommandToPython, sendCommandToPython,
onPythonEvent, onPythonEvent,
offPythonEvent, offPythonEvent,

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
const { sendCommandToPython, isReady } = usePythonBridge() const { sendCommandToPython, isReady, isAvailableOnPlatform, loading } = usePythonBridge()
const { const {
documents, documents,
@ -21,28 +21,48 @@ watch(selectedAction, () => {
selectedTypes.value = availableActions.value[selectedAction.value].allowedTypes selectedTypes.value = availableActions.value[selectedAction.value].allowedTypes
}) })
const resultData = ref<any>(null) // <-- Новое поле для результата
// Синхронизация с КОМПАС // Синхронизация с КОМПАС
async function syncKompas() { async function syncKompas() {
try { try {
const docs = await sendCommandToPython<KompasDocument[]>('open_kompas') // Получаем ответ в новом формате: { status, data, error }
const response = await sendCommandToPython('open_kompas')
if (!docs || !Array.isArray(docs)) { console.log('Полный ответ от Python:', response)
console.warn('Неожиданный ответ от Python:', docs)
documents.value = [] // Проверяем структуру ответа
} else { if (!response || typeof response !== 'object') {
documents.value = docs throw new Error('Некорректный формат ответа: пустой или не объект')
loadActions()
} }
console.warn('Документы из КОМПАС:', docs)
if (response.status === 'error') {
throw new Error(response.error || 'Неизвестная ошибка')
}
if (!response.data || !Array.isArray(response.data)) {
throw new Error('Данные от сервера отсутствуют или имеют неверный формат')
}
// Успешно получили документы
documents.value = response.data
loadActions()
console.warn('Документы из КОМПАС:', documents.value)
} catch (err) { } catch (err) {
console.error('Ошибка при синхронизации с КОМПАС:', err) console.error('Ошибка при синхронизации с КОМПАС:', err)
alert('Не удалось получить список документов из КОМПАС') alert(`Не удалось получить список документов из КОМПАС\n(${err.message})`)
} }
} }
const canRunAction = computed(() => { const canRunAction = computed(() => {
return selectedAction.value && filteredDocuments.value.length > 0 return selectedAction.value && filteredDocuments.value.length > 0
}) })
function updateUIWithResult(data: any) {
resultData.value = data
}
async function runSelectedAction() { async function runSelectedAction() {
if (!selectedAction.value) { if (!selectedAction.value) {
alert('Выберите действие') alert('Выберите действие')
@ -53,18 +73,32 @@ async function runSelectedAction() {
// Отправляем команду на бэкенд // Отправляем команду на бэкенд
const result = await sendCommandToPython(selectedAction.value) const result = await sendCommandToPython(selectedAction.value)
console.log('Результат выполнения:', result) console.log('Полный ответ от сервера:', result)
if (result && result.status === 'success') { // Проверяем, что результат это объект и содержит поле status
alert(`Действие "${selectedAction.value}" выполнено успешно!`) if (typeof result !== 'object' || result === null) {
} else if (result && result.status === 'error') { // alert('Ошибка: получен некорректный формат данных от сервера')
alert(`Ошибка при выполнении: ${result.error || 'Неизвестная ошибка'}`) console.error('Некорректный формат результата:', result)
} else { return
alert('Не удалось получить результат от сервера') }
// Теперь можно безопасно работать с result.status
if (result.status === 'success') {
// alert(` Действие "${selectedAction.value}" выполнено успешно!`)
updateUIWithResult(result.data) // например, выводим данные в интерфейс
}
else if (result.status === 'error') {
const errorMessage = result.error || 'Неизвестная ошибка'
// alert(` Ошибка при выполнении: ${errorMessage}`)
console.error('Ошибка от сервера:', errorMessage)
}
else {
// alert(' Не удалось обработать результат от сервера')
console.warn('Неизвестный статус ответа:', result)
} }
} catch (err) { } catch (err) {
console.error('Ошибка при выполнении действия:', err) console.error('🚨 Ошибка при выполнении действия:', err)
alert('Произошла ошибка при выполнении действия') alert('🔥 Произошла критическая ошибка при выполнении действия')
} }
} }
</script> </script>
@ -72,20 +106,29 @@ async function runSelectedAction() {
<template> <template>
<div class="flex flex-col gap-4 p-4"> <div class="flex flex-col gap-4 p-4">
<h1>Компас прослушка событий от Python</h1> <h1>Компас прослушка событий от Python</h1>
<div v-if="!isAvailableOnPlatform" class="mb-4 p-3 bg-red-100 text-red-800 rounded">
<strong>Мост недоступен на этой платформе.</strong> Возможно, вы запускаете приложение не в Qt/PySide.
</div>
<!-- Статус --> <!-- Статус -->
<div v-if="!isReady" class="mb-4 p-3 bg-yellow-100 text-yellow-800 rounded flex items-center"> <div v-if="!isReady && isAvailableOnPlatform"
class="mb-4 p-3 bg-yellow-100 text-yellow-800 rounded flex items-center">
<strong class="ml-2">Ещё рано отправлять запросы. Идёт инициализация...</strong> <strong class="ml-2">Ещё рано отправлять запросы. Идёт инициализация...</strong>
</div> </div>
<template v-else> <template v-if="isReady && isAvailableOnPlatform">
{{ loading }}
<div> <div>
<!-- Кнопка синхронизации --> <!-- Кнопка синхронизации -->
<button @click="syncKompas" <button @click="syncKompas"
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded mb-4 transition"> class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded mb-4 transition"
:disabled="loading">
🔁 Синхронизировать КОМПАС 🔁 Синхронизировать КОМПАС
</button> </button>
</div> </div>
<div>
{{ resultData }}
</div>
<!-- Сообщение о пустом списке --> <!-- Сообщение о пустом списке -->
<div v-if="!documents.length" class="text-gray-500 italic mt-2"> <div v-if="!documents.length" class="text-gray-500 italic mt-2">
Нет данных о документах. Нажмите "Синхронизировать КОМПАС". Нет данных о документах. Нажмите "Синхронизировать КОМПАС".
@ -117,7 +160,7 @@ async function runSelectedAction() {
</label> </label>
</div> </div>
<!-- Кнопка выполнения действия --> <!-- Кнопка выполнения действия -->
<button type="button" @click="runSelectedAction" :disabled="!canRunAction" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 <button type="button" @click="runSelectedAction" :disabled="!canRunAction || loading" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600
disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none
transition"> transition">
Выполнить действие Выполнить действие