first
This commit is contained in:
@@ -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__/
|
||||
|
||||
# Vite
|
||||
*.timestamp-*-*.mjs
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# control-ui
|
||||
|
||||
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/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<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.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+3576
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "control-ui",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.18.1",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "beta",
|
||||
"vue-router": "^5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node24": "^24.0.4",
|
||||
"@types/node": "^24.13.2",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"npm-run-all2": "^9.0.2",
|
||||
"typescript": "~6.0.0",
|
||||
"vite": "^8.0.16",
|
||||
"vite-plugin-vue-devtools": "^8.1.2",
|
||||
"vue-tsc": "^3.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.18.0 || >=24.12.0"
|
||||
},
|
||||
"overrides": {
|
||||
"vue": "beta",
|
||||
"@vue/compiler-core": "beta",
|
||||
"@vue/compiler-dom": "beta",
|
||||
"@vue/compiler-sfc": "beta",
|
||||
"@vue/compiler-ssr": "beta",
|
||||
"@vue/compiler-vapor": "beta",
|
||||
"@vue/reactivity": "beta",
|
||||
"@vue/runtime-core": "beta",
|
||||
"@vue/runtime-dom": "beta",
|
||||
"@vue/runtime-vapor": "beta",
|
||||
"@vue/server-renderer": "beta",
|
||||
"@vue/shared": "beta",
|
||||
"@vue/compat": "beta"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import { paramsApi } from "@/api.ts";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
const res = ref({});
|
||||
onMounted(async () => {
|
||||
res.value = await paramsApi.list();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>You did it!</h1>
|
||||
<p>
|
||||
Visit {{ res }}
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to
|
||||
read the documentation
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createModelApi } from "@/api_client.ts";
|
||||
import type { Params, ControlCase } from "@/models.ts";
|
||||
|
||||
export const paramsApi = createModelApi<Params>("params");
|
||||
export const controlCaseApi = createModelApi<ControlCase>("control-case");
|
||||
@@ -0,0 +1,176 @@
|
||||
import axios from "axios";
|
||||
|
||||
export interface BaseEntity {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ListParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
search?: string;
|
||||
ordering?: string;
|
||||
[key: string]: string | number | boolean | undefined;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
count: number;
|
||||
next: string | null;
|
||||
previous: string | null;
|
||||
results: T[];
|
||||
}
|
||||
|
||||
export function isPaginatedResponse<T>(
|
||||
payload: PaginatedResponse<T> | T[],
|
||||
): payload is PaginatedResponse<T> {
|
||||
return !Array.isArray(payload);
|
||||
}
|
||||
|
||||
export function unwrapListResponse<T>(
|
||||
payload: PaginatedResponse<T> | T[],
|
||||
): T[] {
|
||||
return Array.isArray(payload) ? payload : payload.results;
|
||||
}
|
||||
|
||||
export interface ModelApi<
|
||||
T extends BaseEntity,
|
||||
CreateDTO = Partial<T>,
|
||||
UpdateDTO = Partial<T>,
|
||||
> {
|
||||
list: (params?: ListParams) => Promise<PaginatedResponse<T> | T[]>;
|
||||
listAll: (params?: ListParams) => Promise<T[]>;
|
||||
retrieve: (id: number) => Promise<T>;
|
||||
create: (payload: CreateDTO) => Promise<T>;
|
||||
update: (id: number, payload: UpdateDTO) => Promise<T>;
|
||||
partialUpdate: (id: number, payload: Partial<UpdateDTO>) => Promise<T>;
|
||||
remove: (id: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export const apiClient = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL ?? "/api",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
function stringifyApiErrorValue(value: unknown): string {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(stringifyApiErrorValue).filter(Boolean).join(", ");
|
||||
}
|
||||
|
||||
if (value && typeof value === "object") {
|
||||
return Object.entries(value)
|
||||
.map(
|
||||
([key, nestedValue]) =>
|
||||
`${key}: ${stringifyApiErrorValue(nestedValue)}`,
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join("; ");
|
||||
}
|
||||
|
||||
return value == null ? "" : String(value);
|
||||
}
|
||||
|
||||
export function formatApiError(
|
||||
error: unknown,
|
||||
fallback = "Не удалось выполнить запрос.",
|
||||
): string {
|
||||
if (!axios.isAxiosError(error)) return fallback;
|
||||
|
||||
const payload = error.response?.data;
|
||||
if (!payload) return error.message || fallback;
|
||||
|
||||
if (typeof payload === "string") return payload;
|
||||
|
||||
if (typeof payload === "object") {
|
||||
const message = Object.entries(payload)
|
||||
.map(([key, value]) => {
|
||||
const text = stringifyApiErrorValue(value);
|
||||
if (!text) return "";
|
||||
if (key === "detail" || key === "non_field_errors") return text;
|
||||
return `${key}: ${text}`;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join("; ");
|
||||
|
||||
return message || fallback;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function createModelApi<
|
||||
T extends BaseEntity,
|
||||
CreateDTO = Partial<T>,
|
||||
UpdateDTO = Partial<T>,
|
||||
>(resource: string): ModelApi<T, CreateDTO, UpdateDTO> {
|
||||
const normalized = resource.endsWith("/") ? resource : `${resource}/`;
|
||||
|
||||
return {
|
||||
async list(params) {
|
||||
const response = await apiClient.get<PaginatedResponse<T> | T[]>(
|
||||
normalized,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async listAll(params) {
|
||||
const firstPage = await apiClient.get<PaginatedResponse<T> | T[]>(
|
||||
normalized,
|
||||
{
|
||||
params: {
|
||||
...(params ?? {}),
|
||||
page: 1,
|
||||
page_size: params?.page_size ?? 100,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const firstPayload = firstPage.data;
|
||||
if (!isPaginatedResponse(firstPayload)) {
|
||||
return firstPayload;
|
||||
}
|
||||
|
||||
const items = [...firstPayload.results];
|
||||
let nextUrl = firstPayload.next;
|
||||
|
||||
while (nextUrl) {
|
||||
const nextPage = await apiClient.get<PaginatedResponse<T>>(nextUrl);
|
||||
items.push(...nextPage.data.results);
|
||||
nextUrl = nextPage.data.next;
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
async retrieve(id) {
|
||||
const response = await apiClient.get<T>(`${normalized}${id}/`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async create(payload) {
|
||||
const response = await apiClient.post<T>(
|
||||
normalized,
|
||||
payload,
|
||||
payload instanceof FormData
|
||||
? { headers: { "Content-Type": "multipart/form-data" } }
|
||||
: undefined,
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async update(id, payload) {
|
||||
const response = await apiClient.put<T>(`${normalized}${id}/`, payload);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async partialUpdate(id, payload) {
|
||||
const response = await apiClient.patch<T>(`${normalized}${id}/`, payload);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async remove(id) {
|
||||
await apiClient.delete(`${normalized}${id}/`);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,23 @@
|
||||
export interface Params {
|
||||
id: number;
|
||||
rel: number;
|
||||
relC: number;
|
||||
le: number;
|
||||
pr: number;
|
||||
pe: number;
|
||||
initialCondition: string;
|
||||
time: number;
|
||||
folderPath: string;
|
||||
}
|
||||
|
||||
export interface ControlCase {
|
||||
id: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
|
||||
name: string;
|
||||
paramsId: number;
|
||||
params: Params;
|
||||
status: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [],
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
// Extra safety for array and object lookups, but may have false positives.
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Path mapping for cleaner imports.
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||
// Specified here to keep it out of the root directory.
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
|
||||
{
|
||||
"extends": "@tsconfig/node24/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
// Most tools use transpilation instead of Node.js's native type-stripping.
|
||||
// Bundler mode provides a smoother developer experience.
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
// Include Node.js types and avoid accidentally including other `@types/*` packages.
|
||||
"types": ["node"],
|
||||
|
||||
// Disable emitting output during `vue-tsc --build`, which is used for type-checking only.
|
||||
"noEmit": true,
|
||||
|
||||
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||
// Specified here to keep it out of the root directory.
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
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()],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
"^/api": {
|
||||
target: "http://127.0.0.1:8080",
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user