Initial commit
This commit is contained in:
39
.gitignore
vendored
Normal file
39
.gitignore
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Cypress
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Vitest
|
||||||
|
__screenshots__/
|
||||||
|
|
||||||
|
test-results/
|
||||||
|
playwright-report/
|
||||||
6
.vscode/extensions.json
vendored
Normal file
6
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"ms-playwright.playwright"
|
||||||
|
]
|
||||||
|
}
|
||||||
57
README.md
Normal file
57
README.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# test
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Recommended Browser Setup
|
||||||
|
|
||||||
|
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||||
|
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||||
|
- Firefox:
|
||||||
|
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||||
|
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run End-to-End Tests with [Playwright](https://playwright.dev)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Install browsers for the first run
|
||||||
|
npx playwright install
|
||||||
|
|
||||||
|
# When testing on CI, must build the project first
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Runs the end-to-end tests
|
||||||
|
npm run test:e2e
|
||||||
|
# Runs the tests only on Chromium
|
||||||
|
npm run test:e2e -- --project=chromium
|
||||||
|
# Runs the tests of a specific file
|
||||||
|
npm run test:e2e -- tests/example.spec.ts
|
||||||
|
# Runs the tests in debug mode
|
||||||
|
npm run test:e2e -- --debug
|
||||||
|
```
|
||||||
8
e2e/vue.spec.js
Normal file
8
e2e/vue.spec.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
// See here how to get started:
|
||||||
|
// https://playwright.dev/docs/intro
|
||||||
|
test('visits the app root url', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
await expect(page.locator('h1')).toHaveText('You did it!');
|
||||||
|
})
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/src/assets/logo.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
jsconfig.json
Normal file
8
jsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
0
package-lock.json
generated
Normal file
0
package-lock.json
generated
Normal file
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"test:e2e": "playwright test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.13.2",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
|
"vue": "^3.5.25"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.57.0",
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.2",
|
||||||
|
"vite": "^7.3.0",
|
||||||
|
"vite-plugin-vue-devtools": "^8.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
110
playwright.config.js
Normal file
110
playwright.config.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import process from 'node:process'
|
||||||
|
import { defineConfig, devices } from '@playwright/test'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './e2e',
|
||||||
|
/* Maximum time one test can run for. */
|
||||||
|
timeout: 30 * 1000,
|
||||||
|
expect: {
|
||||||
|
/**
|
||||||
|
* Maximum time expect() should wait for the condition to be met.
|
||||||
|
* For example in `await expect(locator).toHaveText();`
|
||||||
|
*/
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: 'html',
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||||
|
actionTimeout: 0,
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: process.env.CI ? 'http://localhost:4173' : 'http://localhost:5173',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
|
||||||
|
/* Only on CI systems run the tests headless */
|
||||||
|
headless: !!process.env.CI,
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Firefox'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Safari'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: {
|
||||||
|
// ...devices['Pixel 5'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: {
|
||||||
|
// ...devices['iPhone 12'],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: {
|
||||||
|
// channel: 'msedge',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: {
|
||||||
|
// channel: 'chrome',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||||
|
// outputDir: 'test-results/',
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
/**
|
||||||
|
* Use the dev server by default for faster feedback loop.
|
||||||
|
* Use the preview server on CI for more realistic testing.
|
||||||
|
* Playwright will re-use the local server if there is already a dev-server running.
|
||||||
|
*/
|
||||||
|
command: process.env.CI ? 'npm run preview' : 'npm run dev',
|
||||||
|
port: process.env.CI ? 4173 : 5173,
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
})
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
59
src/App.vue
Normal file
59
src/App.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, watch } from "vue";
|
||||||
|
import My_naw from "./components/My_naw.vue";
|
||||||
|
import Section from "./components/Section.vue";
|
||||||
|
import Setings from "./components/Setings.vue";
|
||||||
|
import Authe from "./components/Authe.vue";
|
||||||
|
import axios from "axios";
|
||||||
|
// Состояния страницы и данные для входа
|
||||||
|
const currentPage = ref("admin-panel");
|
||||||
|
const items = ref([]);
|
||||||
|
|
||||||
|
// Универсальная функция для получения данных
|
||||||
|
const fetchData = async (url, targetRef) => {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(url)
|
||||||
|
targetRef.value = data
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Ошибка при получении данных с ${url}:`, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Обертки для вызова
|
||||||
|
const loadItems = () => fetchData("http://45.129.78.228:8002/records_all", items) //http://127.0.0.1:8002/records_all http://45.129.78.228:8002/records_all
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadItems()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(items, loadItems)
|
||||||
|
|
||||||
|
function handleUpdate(newValue) {
|
||||||
|
currentPage.value = newValue; // изменение значения в родителе
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Authe :currentPage="currentPage" @update="handleUpdate"/>
|
||||||
|
<div v-if="currentPage === 'rezylt'">
|
||||||
|
<div class="sm:flex">
|
||||||
|
|
||||||
|
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
|
||||||
|
<Section :items="items"/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="currentPage === 'setings'">
|
||||||
|
<div class="sm:flex">
|
||||||
|
<My_naw :currentPage="currentPage" @update="handleUpdate"/>
|
||||||
|
<Setings/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Добавьте стили по необходимости */
|
||||||
|
</style>
|
||||||
BIN
src/assets/href.webp
Normal file
BIN
src/assets/href.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
16
src/assets/main.css
Normal file
16
src/assets/main.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* Для темной темы */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body {
|
||||||
|
background: #111;
|
||||||
|
/* другие стили для темной темы */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Для светлой темы (по умолчанию или явно) */
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
body {
|
||||||
|
background: #ccc; /* или любой другой цвет */
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/components/Authe.vue
Normal file
92
src/components/Authe.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
// Получение props
|
||||||
|
const props = defineProps({
|
||||||
|
currentPage: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Объявление события для обновления страницы
|
||||||
|
const emit = defineEmits(["update"]);
|
||||||
|
|
||||||
|
const login = ref("");
|
||||||
|
const password = ref("");
|
||||||
|
const authError = ref(false);
|
||||||
|
|
||||||
|
// Функция авторизации
|
||||||
|
const auth_my = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post("http://45.129.78.228:8004/login", {
|
||||||
|
username: login.value,
|
||||||
|
password: password.value,
|
||||||
|
});
|
||||||
|
if (response.data.message === "Login successful") {
|
||||||
|
authError.value = false;
|
||||||
|
emit("update", "rezylt");
|
||||||
|
} else {
|
||||||
|
authError.value = true;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
authError.value = true;
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обработка глобального нажатия Enter
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
auth_my();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавляем глобальный слушатель при монтировании
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удаляем при размонтировании
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Переход на стартовую страницу (сброс данных)
|
||||||
|
function showStartPage() {
|
||||||
|
emit("update", "admin-panel");
|
||||||
|
login.value = "";
|
||||||
|
password.value = "";
|
||||||
|
authError.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="props.currentPage === 'admin-panel'"
|
||||||
|
class="p-4 bg-white rounded shadow max-w-md mx-auto mt-10 dark:bg-gray-800 dark:text-neutral-300"
|
||||||
|
>
|
||||||
|
<h2>Вход для администратора</h2>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Логин:</label>
|
||||||
|
<input v-model="login" type="text" class="border p-1 w-full" />
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Пароль:</label>
|
||||||
|
<input v-model="password" type="password" class="border p-1 w-full" />
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="auth_my"
|
||||||
|
class="mr-2 bg-teal-600 hover:bg-teal-800 text-white px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
@click="showStartPage"
|
||||||
|
class="dark:bg-gray-600 dark:hover:bg-gray-700 bg-gray-500 hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
<div v-if="authError" class="mt-2 text-red-600">
|
||||||
|
Неверный логин или пароль
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
48
src/components/Mob_naw.vue
Normal file
48
src/components/Mob_naw.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<button class="rounded-md bg-white/10 px-2.5 py-1.5 text-sm font-semibold text-white inset-ring inset-ring-white/5 hover:bg-white/20" @click="open = true">Open drawer</button>
|
||||||
|
<TransitionRoot as="template" :show="open">
|
||||||
|
<Dialog class="relative z-10" @close="open = false">
|
||||||
|
<TransitionChild as="template" enter="ease-in-out duration-500" enter-from="opacity-0" enter-to="" leave="ease-in-out duration-500" leave-from="" leave-to="opacity-0">
|
||||||
|
<div class="fixed inset-0 bg-gray-900/50 transition-opacity"></div>
|
||||||
|
</TransitionChild>
|
||||||
|
|
||||||
|
<div class="fixed inset-0 overflow-hidden">
|
||||||
|
<div class="absolute inset-0 overflow-hidden">
|
||||||
|
<div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
|
||||||
|
<TransitionChild as="template" enter="transform transition ease-in-out duration-500 sm:duration-700" enter-from="translate-x-full" enter-to="translate-x-0" leave="transform transition ease-in-out duration-500 sm:duration-700" leave-from="translate-x-0" leave-to="translate-x-full">
|
||||||
|
<DialogPanel class="pointer-events-auto relative w-screen max-w-md">
|
||||||
|
<TransitionChild as="template" enter="ease-in-out duration-500" enter-from="opacity-0" enter-to="" leave="ease-in-out duration-500" leave-from="" leave-to="opacity-0">
|
||||||
|
<div class="absolute top-0 left-0 -ml-8 flex pt-4 pr-2 sm:-ml-10 sm:pr-4">
|
||||||
|
<button type="button" class="relative rounded-md text-gray-400 hover:text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500" @click="open = false">
|
||||||
|
<span class="absolute -inset-2.5"></span>
|
||||||
|
<span class="sr-only">Close panel</span>
|
||||||
|
<XMarkIcon class="size-6" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</TransitionChild>
|
||||||
|
<div class="relative flex h-full flex-col overflow-y-auto bg-gray-800 py-6 shadow-xl after:absolute after:inset-y-0 after:left-0 after:w-px after:bg-white/10">
|
||||||
|
<div class="px-4 sm:px-6">
|
||||||
|
<DialogTitle class="text-base font-semibold text-white">Panel title</DialogTitle>
|
||||||
|
</div>
|
||||||
|
<div class="relative mt-6 flex-1 px-4 sm:px-6">
|
||||||
|
<!-- Your content -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogPanel>
|
||||||
|
</TransitionChild>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</TransitionRoot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
||||||
|
import { XMarkIcon } from '@heroicons/vue/24/outline'
|
||||||
|
|
||||||
|
const open = ref(true)
|
||||||
|
</script>
|
||||||
53
src/components/My_naw.vue
Normal file
53
src/components/My_naw.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div class=" sticky top-0 w-full sm:w-1/5 sm:h-screen bg-white z-10 pt-5 md:p-5 dark:bg-gray-800">
|
||||||
|
<!-- Фиксированный левый блок -->
|
||||||
|
<div v-if="currentPage === 'rezylt'" class="flex sm:flex-col">
|
||||||
|
<button
|
||||||
|
@click="ValueRezylt"
|
||||||
|
class="bg-gray-600 w-full text-white min-w-30 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Результат
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="ValueSeting"
|
||||||
|
class="bg-gray-300 sm:mt-3 w-full min-w-30 hover:text-white hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Настройка
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="currentPage === 'setings'" class="flex sm:flex-col">
|
||||||
|
<button
|
||||||
|
@click="ValueRezylt"
|
||||||
|
class="bg-gray-300 w-full min-w-30 hover:text-white hover:bg-gray-600 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Результат
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="ValueSeting"
|
||||||
|
class="bg-gray-600 sm:mt-3 w-full text-white min-w-30 px-4 py-2 rounded cursor-pointer"
|
||||||
|
>
|
||||||
|
Настройка
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
currentPage: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Макросы Vue 3.3+ не требуют явного импорта
|
||||||
|
const emit = defineEmits(["update"]);
|
||||||
|
|
||||||
|
function ValueSeting() {
|
||||||
|
emit("update", "setings");
|
||||||
|
}
|
||||||
|
|
||||||
|
function ValueRezylt() {
|
||||||
|
emit("update", "rezylt");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
35
src/components/Section.vue
Normal file
35
src/components/Section.vue
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<!-- AdminLogin.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="w-full sm:w-4/5 dark:text-neutral-300">
|
||||||
|
<div class="bg-white flex justify-between p-5 dark:bg-gray-800 ">
|
||||||
|
<h1>Результат:</h1>
|
||||||
|
<Time />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<Stat
|
||||||
|
v-for="item in items"
|
||||||
|
:translation_text="item.translation_text"
|
||||||
|
:original_text="item.original_text"
|
||||||
|
:url="item.url"
|
||||||
|
:title="item.title"
|
||||||
|
:article_date="item.article_date"
|
||||||
|
:short_text="item.short_text"
|
||||||
|
:category="item.category"
|
||||||
|
:parsed_at="item.parsed_at"
|
||||||
|
:status = "item.status"
|
||||||
|
:viewed = "item.viewed"
|
||||||
|
:other = "item.other"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import Stat from "./Stat.vue";
|
||||||
|
import Time from "./Time.vue";
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
items: Array,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
240
src/components/Setings.vue
Normal file
240
src/components/Setings.vue
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full sm:w-4/5 dark:text-neutral-300">
|
||||||
|
<!-- Ввод времени -->
|
||||||
|
<div class="bg-white p-4 mb-4 flex-colum sm:flex dark:bg-gray-800">
|
||||||
|
<div class="w-full sm:max-w-100 flex">
|
||||||
|
<input
|
||||||
|
v-model="time"
|
||||||
|
type="text"
|
||||||
|
placeholder="01.01.2026"
|
||||||
|
class="dark:bg-gray-900 min-w-30 border-slate-100 shadow rounded-xl w-1/2 p-3"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="dark:bg-orange-500 hover:dark:bg-orange-600 ml-4 shadow text-white bg-sky-700 w-1/2 hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
|
||||||
|
@click="start_parser_1"
|
||||||
|
>
|
||||||
|
Парсить 1 источник
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="dark:bg-orange-500 hover:dark:bg-orange-600 sm:ml-2 shadow mt-1 sm:mt-0 text-white w-full sm:max-w-50 bg-sky-700 hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
|
||||||
|
@click="start_parser_2"
|
||||||
|
>
|
||||||
|
Парсить 2 источник
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Список источников и промт -->
|
||||||
|
<div
|
||||||
|
class="dark:bg-gray-800 sm:m-5 bg-white p-4 hover:-translate-y-2 hover:shadow-2xl border-slate-100 rounded-xl transition"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex-colum sm:flex items-center mb-4 border-slate-900 justify-between"
|
||||||
|
>
|
||||||
|
<div class="w-full sm:max-w-60 flex">
|
||||||
|
<button
|
||||||
|
v-for="(source, index) in sources"
|
||||||
|
:key="source.name"
|
||||||
|
@click="selectSource(index)"
|
||||||
|
class="dark:bg-neutral-500 hover:dark:bg-neutral-600 p-2 mr-2 rounded-xl bg-sky-700 hover:bg-sky-900 w-1/2 text-white cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ source.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопка сохранения источников -->
|
||||||
|
<button
|
||||||
|
class="shadow mt-1 sm:mt-0 text-white bg-green-500 hover:bg-green-600 rounded-xl w-full sm:max-w-60 px-2 min-h-11 cursor-pointer"
|
||||||
|
@click="saveSources"
|
||||||
|
>
|
||||||
|
Сохранить изменения
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Редактируемый промт -->
|
||||||
|
<div class=" ">
|
||||||
|
<textarea
|
||||||
|
v-model="promt"
|
||||||
|
class="w-full min-h-100 rounded-xl p-2 border-2 border-neutral-300"
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Список логов -->
|
||||||
|
<div
|
||||||
|
class="dark:bg-gray-800 mt-5 sm:m-5 bg-white p-4 hover:-translate-y-2 hover:shadow-2xl border-slate-100 rounded-xl transition"
|
||||||
|
>
|
||||||
|
<!-- Кнопка для переключения -->
|
||||||
|
<button
|
||||||
|
@click="toggleLogs"
|
||||||
|
class="dark:bg-orange-500 hover:dark:bg-orange-600 shadow text-white bg-sky-700 w-full hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ showLogs ? "Скрыть логи" : "Показать логи" }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Блок логов, показывается или скрывается -->
|
||||||
|
<div
|
||||||
|
class="border-1 border-neutral-300 p-2 mt-2 rounded-xl"
|
||||||
|
v-if="showLogs"
|
||||||
|
>
|
||||||
|
<div v-for="(log, index) in logs" :key="index">
|
||||||
|
{{ log }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="dark:bg-gray-800 mt-5 sm:m-5 bg-white p-4 hover:-translate-y-2 hover:shadow-2xl border-slate-100 rounded-xl transition"
|
||||||
|
>
|
||||||
|
<!-- Кнопка для переключения -->
|
||||||
|
<button
|
||||||
|
@click="toggletask"
|
||||||
|
class="dark:bg-orange-500 hover:dark:bg-orange-600 shadow text-white bg-sky-700 w-full hover:bg-sky-900 rounded-xl px-2 min-h-11 cursor-pointer"
|
||||||
|
>
|
||||||
|
{{ showtask ? "Скрыть задачи" : "Показать задачи" }}
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="border-1 border-neutral-300 p-2 mt-2 rounded-xl overflow-x-auto"
|
||||||
|
v-if="showtask"
|
||||||
|
>
|
||||||
|
<table class="w-full min-w-max">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="px-2 py-2 text-left">Status</th>
|
||||||
|
<th class="px-2 py-2 text-left">Source URL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="task in tasks" :key="task.id">
|
||||||
|
<td
|
||||||
|
class="px-2 py-2 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-full"
|
||||||
|
>
|
||||||
|
{{ task.status }}
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
class="px-2 py-2 whitespace-nowrap overflow-hidden overflow-ellipsis max-w-full"
|
||||||
|
>
|
||||||
|
{{ task.source_url }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, watch } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const sources = ref([
|
||||||
|
{ name: "source1", url: "eee", prompt: "" },
|
||||||
|
{ name: "source2", url: "https://example2.com/api", prompt: "" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const currentSource = ref(sources.value[0]);
|
||||||
|
const time = ref("");
|
||||||
|
const promt = ref("Начальный текст");
|
||||||
|
const logs = ref([]);
|
||||||
|
const tasks = ref([]);
|
||||||
|
const showLogs = ref(false); // скрыто по умолчанию
|
||||||
|
const showtask = ref(false); // скрыто по умолчанию
|
||||||
|
|
||||||
|
// функция для переключения видимости логов
|
||||||
|
function toggleLogs() {
|
||||||
|
showLogs.value = !showLogs.value;
|
||||||
|
if (showLogs.value) {
|
||||||
|
load_log();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const load_log = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get("http://45.129.78.228:8001/logs");
|
||||||
|
logs.value = response.data.logs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при загрузке настроек:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// функция для переключения видимости задач
|
||||||
|
function toggletask() {
|
||||||
|
showtask.value = !showtask.value;
|
||||||
|
if (showtask.value) {
|
||||||
|
loadTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadTasks = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
"http://45.129.78.228:8001/get_tasks_offset",
|
||||||
|
);
|
||||||
|
// Предположим, что ответ уже массив задач
|
||||||
|
tasks.value = response.data; // или response.data если это массив
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при загрузке задач:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Загрузка настроек при монтировании
|
||||||
|
onMounted(() => {
|
||||||
|
loadSettings();
|
||||||
|
// load_log();
|
||||||
|
});
|
||||||
|
|
||||||
|
// watch(logs, load_log);
|
||||||
|
|
||||||
|
// Получение настроек с сервера
|
||||||
|
const loadSettings = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get("http://45.129.78.228:8001/settings");
|
||||||
|
sources.value = response.data.sources;
|
||||||
|
currentSource.value = sources.value[0];
|
||||||
|
promt.value = currentSource.value.prompt;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при загрузке настроек:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Выбор источника
|
||||||
|
const selectSource = (index) => {
|
||||||
|
currentSource.value = sources.value[index];
|
||||||
|
promt.value = currentSource.value.prompt;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Сохранение источников
|
||||||
|
const saveSources = async () => {
|
||||||
|
try {
|
||||||
|
await axios.post("http://45.129.78.228:8001/settings", {
|
||||||
|
name: currentSource.value.name,
|
||||||
|
url: "",
|
||||||
|
prompt: promt.value,
|
||||||
|
});
|
||||||
|
// Здесь можно отправить на сервер, если нужно
|
||||||
|
loadSettings();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при сохранении источников:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Запуск парсинга 1
|
||||||
|
const start_parser_1 = async () => {
|
||||||
|
try {
|
||||||
|
await axios.post("http://45.129.78.228:8001/parser_1", {
|
||||||
|
time: time.value,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка запроса parser_1:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Запуск парсинга 2
|
||||||
|
const start_parser_2 = async () => {
|
||||||
|
try {
|
||||||
|
await axios.post("http://45.129.78.228:8001/parser_2");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка запроса parser_2:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
106
src/components/Stat.vue
Normal file
106
src/components/Stat.vue
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
url: String,
|
||||||
|
parsed_at: String,
|
||||||
|
title: String,
|
||||||
|
category: String,
|
||||||
|
short_text: String,
|
||||||
|
article_date: String,
|
||||||
|
original_text: String,
|
||||||
|
translation_text: String,
|
||||||
|
other: String,
|
||||||
|
viewed: Boolean,
|
||||||
|
status: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
//Код для раскрытия/скрытия карточки
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const cardRef = ref(null);
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
isOpen.value = true; //!isOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickOutside(event) {
|
||||||
|
if (cardRef.value && !cardRef.value.contains(event.target)) {
|
||||||
|
isOpen.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewed_b = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
"http://45.129.78.228:8002/update_viewed_status",
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
url: props.url,
|
||||||
|
viewed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener("click", handleClickOutside);
|
||||||
|
});
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
document.removeEventListener("click", handleClickOutside);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="bg-white dark:bg-gray-800 hover:-translate-y-2 hover:shadow-2xl rounded-xl transition pb-1 mb-3"
|
||||||
|
:class="{ 'opacity-50': props.viewed }"
|
||||||
|
><!--:class="{ 'opacity-50': props.viewed }" обработчик прочитанности-->
|
||||||
|
<div ref="cardRef" class="cursor-pointer" @click="toggle">
|
||||||
|
<div class="flex justify-between p-3">
|
||||||
|
<p>{{ article_date }}</p>
|
||||||
|
<a
|
||||||
|
:href="url"
|
||||||
|
class="max-w-100 text-blue-600 dark:text-gray-400 hover:text-blue-800 underline flex"
|
||||||
|
style="overflow: hidden; white-space: nowrap; text-overflow: ellipsis"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/src/assets/href.webp"
|
||||||
|
class="w-5 h-6 mr-2"
|
||||||
|
/>
|
||||||
|
{{ url }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
|
||||||
|
<p>{{ title }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Вариант с Tailwind: показывать/скрывать по состоянию -->
|
||||||
|
<div v-show="isOpen" class="transition-all">
|
||||||
|
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
|
||||||
|
<p>{{ short_text }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
|
||||||
|
<p>{{ translation_text }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
|
||||||
|
<p>{{ category }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="m-3 dark:bg-gray-900 shadow rounded-xl p-3">
|
||||||
|
<p>{{ original_text }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="ml-4 shadow mb-5 bg-lime-500 dark:bg-orange-500 hover:dark:bg-orange-600 hover:dark:text-neutral-50 hover:bg-lime-300 hover:text-stone-900 rounded-xl w-40 h-10 text-white cursor-pointer"
|
||||||
|
@click="viewed_b"
|
||||||
|
>
|
||||||
|
Прочитано
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
45
src/components/Time.vue
Normal file
45
src/components/Time.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>Дата и время в Пекине: <br>{{ currentDateTime }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentDateTime: '', // строка с текущими датой и временем
|
||||||
|
timer: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateTime() {
|
||||||
|
const options = {
|
||||||
|
timeZone: 'Asia/Shanghai',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
};
|
||||||
|
const formatter = new Intl.DateTimeFormat('zh-CN', options);
|
||||||
|
const parts = formatter.formatToParts(new Date());
|
||||||
|
|
||||||
|
const dateParts = {};
|
||||||
|
parts.forEach(({ type, value }) => {
|
||||||
|
dateParts[type] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.currentDateTime = `${dateParts.year}-${dateParts.month}-${dateParts.day} ${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateTime();
|
||||||
|
this.timer = setInterval(this.updateTime, 1000);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
6
src/main.js
Normal file
6
src/main.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import './assets/main.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
createApp(App).mount('#app')
|
||||||
15
vite.config.js
Normal file
15
vite.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
|
import vueDevTools from "vite-plugin-vue-devtools";
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue(), vueDevTools(), tailwindcss()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user