bx-865-apps #1
|
@ -8579,7 +8579,7 @@
|
||||||
"kind": 5,
|
"kind": 5,
|
||||||
"importPath": "back.back.settings",
|
"importPath": "back.back.settings",
|
||||||
"description": "back.back.settings",
|
"description": "back.back.settings",
|
||||||
"peekOfCode": "CSRF_TRUSTED_ORIGINS = (\n \"https://demo.kustarshina.ru\",\n \"http://localhost\",\n \"http://localhost:3011\",\n \"http://192.168.103.159\",\n)\nCORS_ORIGIN_ALLOW_ALL = False\nCORS_ORIGIN_WHITELIST = [\n \"null\",\n \"http://localhost\",",
|
"peekOfCode": "CSRF_TRUSTED_ORIGINS = (\n \"https://demo.kustarshina.ru\",\n \"http://localhost\",\n \"http://localhost:3011\",\n \"http://localhost:4173\",\n \"http://localhost:5173\",\n \"http://192.168.103.159:3011\",\n \"http://192.168.103.159\",\n)\nCORS_ORIGIN_ALLOW_ALL = False",
|
||||||
"detail": "back.back.settings",
|
"detail": "back.back.settings",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8588,7 +8588,7 @@
|
||||||
"kind": 5,
|
"kind": 5,
|
||||||
"importPath": "back.back.settings",
|
"importPath": "back.back.settings",
|
||||||
"description": "back.back.settings",
|
"description": "back.back.settings",
|
||||||
"peekOfCode": "CORS_ORIGIN_ALLOW_ALL = False\nCORS_ORIGIN_WHITELIST = [\n \"null\",\n \"http://localhost\",\n \"http://localhost:3000\",\n \"http://localhost:3011\",\n \"http://localhost:4173\",\n \"http://localhost:5173\",\n \"http://localhost:8000\",\n \"http://127.0.0.1\",",
|
"peekOfCode": "CORS_ORIGIN_ALLOW_ALL = False\nCORS_ORIGIN_WHITELIST = [\n \"null\",\n \"http://localhost\",\n \"http://localhost:3011\",\n \"http://localhost:4173\",\n \"http://localhost:5173\",\n \"http://localhost:8000\",\n \"http://192.168.103.159:3011\",\n \"http://127.0.0.1\",",
|
||||||
"detail": "back.back.settings",
|
"detail": "back.back.settings",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
@ -8597,7 +8597,7 @@
|
||||||
"kind": 5,
|
"kind": 5,
|
||||||
"importPath": "back.back.settings",
|
"importPath": "back.back.settings",
|
||||||
"description": "back.back.settings",
|
"description": "back.back.settings",
|
||||||
"peekOfCode": "CORS_ORIGIN_WHITELIST = [\n \"null\",\n \"http://localhost\",\n \"http://localhost:3000\",\n \"http://localhost:3011\",\n \"http://localhost:4173\",\n \"http://localhost:5173\",\n \"http://localhost:8000\",\n \"http://127.0.0.1\",\n \"http://192.168.103.159\",",
|
"peekOfCode": "CORS_ORIGIN_WHITELIST = [\n \"null\",\n \"http://localhost\",\n \"http://localhost:3011\",\n \"http://localhost:4173\",\n \"http://localhost:5173\",\n \"http://localhost:8000\",\n \"http://192.168.103.159:3011\",\n \"http://127.0.0.1\",\n \"http://192.168.103.159\",",
|
||||||
"detail": "back.back.settings",
|
"detail": "back.back.settings",
|
||||||
"documentation": {}
|
"documentation": {}
|
||||||
},
|
},
|
||||||
|
|
|
@ -44,17 +44,20 @@ CSRF_TRUSTED_ORIGINS = (
|
||||||
"https://demo.kustarshina.ru",
|
"https://demo.kustarshina.ru",
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"http://localhost:3011",
|
"http://localhost:3011",
|
||||||
|
"http://localhost:4173",
|
||||||
|
"http://localhost:5173",
|
||||||
|
"http://192.168.103.159:3011",
|
||||||
"http://192.168.103.159",
|
"http://192.168.103.159",
|
||||||
)
|
)
|
||||||
CORS_ORIGIN_ALLOW_ALL = False
|
CORS_ORIGIN_ALLOW_ALL = False
|
||||||
CORS_ORIGIN_WHITELIST = [
|
CORS_ORIGIN_WHITELIST = [
|
||||||
"null",
|
"null",
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"http://localhost:3000",
|
|
||||||
"http://localhost:3011",
|
"http://localhost:3011",
|
||||||
"http://localhost:4173",
|
"http://localhost:4173",
|
||||||
"http://localhost:5173",
|
"http://localhost:5173",
|
||||||
"http://localhost:8000",
|
"http://localhost:8000",
|
||||||
|
"http://192.168.103.159:3011",
|
||||||
"http://127.0.0.1",
|
"http://127.0.0.1",
|
||||||
"http://192.168.103.159",
|
"http://192.168.103.159",
|
||||||
"http://192.168.103.159:3000",
|
"http://192.168.103.159:3000",
|
||||||
|
|
|
@ -7,7 +7,9 @@ export {}
|
||||||
|
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
copy: typeof import('./src/components/Floorplan copy.vue')['default']
|
||||||
Floorplan: typeof import('./src/components/Floorplan.vue')['default']
|
Floorplan: typeof import('./src/components/Floorplan.vue')['default']
|
||||||
|
FloorplanItem: typeof import('./src/components/FloorplanItem.vue')['default']
|
||||||
Game: typeof import('./src/components/Game.vue')['default']
|
Game: typeof import('./src/components/Game.vue')['default']
|
||||||
Home: typeof import('./src/components/Home.vue')['default']
|
Home: typeof import('./src/components/Home.vue')['default']
|
||||||
IMdiFile: typeof import('~icons/mdi/file')['default']
|
IMdiFile: typeof import('~icons/mdi/file')['default']
|
||||||
|
|
|
@ -1,133 +1,25 @@
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped></style>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, } from 'vue';
|
||||||
import PF, { Grid } from 'pathfinding'
|
|
||||||
|
|
||||||
import { useFloorplanStore } from '../stores/floorplan';
|
import { useFloorplanStore } from '../stores/floorplan';
|
||||||
|
|
||||||
type PathItem = { path: string, unwalkable: boolean, x: number, y: number }
|
|
||||||
const floorplan = useFloorplanStore()
|
const floorplan = useFloorplanStore()
|
||||||
|
|
||||||
const canvasElement = ref();
|
|
||||||
const context = ref();
|
|
||||||
|
|
||||||
const grid = ref<Grid>()
|
|
||||||
const startPoint = ref<{ x: number, y: number }>({ x: 25, y: 40 })
|
|
||||||
const endPoint = ref<{ x: number, y: number }>()
|
|
||||||
const startToEndPath = ref()
|
|
||||||
|
|
||||||
const plan = useFloorplanStore()
|
|
||||||
const paths = ref<PathItem[]>([])
|
|
||||||
|
|
||||||
const finder = new PF.AStarFinder();
|
|
||||||
|
|
||||||
const newDraw = () => {
|
|
||||||
endPoint.value = undefined
|
|
||||||
startToEndPath.value = undefined
|
|
||||||
|
|
||||||
context.value = canvasElement.value?.getContext('2d') || undefined;
|
|
||||||
const lines = plan.np_array
|
|
||||||
lines.forEach((line, indexY) => {
|
|
||||||
line.forEach((point, indexX) => {
|
|
||||||
if (canvasElement.value && context.value) {
|
|
||||||
context.value.fillStyle = point > 0 ? 'purple' : 'WHITE'
|
|
||||||
context.value.fillRect(indexX, indexY, 1, 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// if (canvasElement.value && context.value) {
|
|
||||||
// context.value.clearRect(0, 0, canvasElement.value.width, canvasElement.value.height);
|
|
||||||
// if (!plan.value.hasOwnProperty('paths')) return
|
|
||||||
// (plan.value).paths.forEach((path: string) => {
|
|
||||||
// if (!context.value) return
|
|
||||||
// context.value.fillStyle = 'rgba(255, 0, 255, 0.25)'
|
|
||||||
// context.value.fill(new Path2D(path));
|
|
||||||
|
|
||||||
// context.value.strokeStyle = 'blue'
|
|
||||||
// context.value.stroke(new Path2D(path));
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
const quantum_lines = plan.prepared_array
|
|
||||||
quantum_lines.forEach((line, indexY) => {
|
|
||||||
line.forEach((point, indexX) => {
|
|
||||||
const targetX = indexX * plan.chunkSize
|
|
||||||
const targetY = indexY * plan.chunkSize
|
|
||||||
paths.value.push({
|
|
||||||
path: `M${targetX} ${targetY} ${targetX + plan.chunkSize} ${targetY} ${targetX + plan.chunkSize} ${targetY + plan.chunkSize} ${targetX} ${targetY + plan.chunkSize}Z`,
|
|
||||||
unwalkable: !!point,
|
|
||||||
x: indexX,
|
|
||||||
y: indexY,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
grid.value = new PF.Grid(plan.prepared_array.map(y => y.map(x => x > 0 ? 1 : 0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const findPath = async () => {
|
|
||||||
if (!endPoint.value) return
|
|
||||||
const localPath = finder.findPath(
|
|
||||||
Math.round(startPoint.value.x),
|
|
||||||
Math.round(startPoint.value.y),
|
|
||||||
Math.round(endPoint.value.x),
|
|
||||||
Math.round(endPoint.value.y),
|
|
||||||
(grid.value?.clone() as Grid)
|
|
||||||
);
|
|
||||||
startToEndPath.value = localPath
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPointSvg = (item: PathItem) => {
|
|
||||||
// startToEndPath.value = []
|
|
||||||
endPoint.value = { x: item.x, y: item.y }
|
|
||||||
findPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await floorplan.getData()
|
await floorplan.getList()
|
||||||
newDraw()
|
|
||||||
})
|
})
|
||||||
const cw = 1920
|
|
||||||
const ch = 800
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="container" style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
|
<div class="container">
|
||||||
<canvas ref="canvasElement" :width="cw" :height="ch"></canvas>
|
<ul>
|
||||||
<svg ref="svgElement" :width="cw" :height="ch" style="position: absolute;">
|
<li v-for="item in floorplan.items">
|
||||||
<path v-for="item in paths" :d="item.path" @click="setPointSvg(item)" :class="[
|
<RouterLink :to="`/floorplan/${item.id}`">
|
||||||
{ 'unwalkable': item.unwalkable },
|
{{ item.title }}
|
||||||
{ 'endPoint': (endPoint && item.x == endPoint.x && item.y == endPoint.y) },
|
</RouterLink>
|
||||||
{ 'startPoint': (startPoint && item.x == startPoint.x && item.y == startPoint.y) },
|
</li>
|
||||||
{ 'pathPoint': (startToEndPath && startToEndPath.find((el: number[]) => el[0] == item.x && el[1] == item.y)) },
|
</ul>
|
||||||
]">
|
|
||||||
</path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
svg path {
|
|
||||||
fill: transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path:hover {
|
|
||||||
fill: red
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path.unwalkable {
|
|
||||||
/* fill: rgba(0, 0, 0, 0.5); */
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path.endPoint {
|
|
||||||
fill: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path.startPoint {
|
|
||||||
fill: lawngreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path.pathPoint {
|
|
||||||
fill: gold;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,130 @@
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import PF, { Grid } from 'pathfinding'
|
||||||
|
|
||||||
|
import { useFloorplanStore } from '../stores/floorplan';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
type PathItem = { path: string, unwalkable: boolean, x: number, y: number }
|
||||||
|
const floorplan = useFloorplanStore()
|
||||||
|
|
||||||
|
const canvasElement = ref();
|
||||||
|
const context = ref();
|
||||||
|
|
||||||
|
const grid = ref<Grid>()
|
||||||
|
const startPoint = ref<{ x: number, y: number }>({ x: 25, y: 40 })
|
||||||
|
const endPoint = ref<{ x: number, y: number }>()
|
||||||
|
const startToEndPath = ref()
|
||||||
|
|
||||||
|
const plan = useFloorplanStore()
|
||||||
|
const paths = ref<PathItem[]>([])
|
||||||
|
|
||||||
|
const finder = new PF.AStarFinder();
|
||||||
|
|
||||||
|
const nextFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const newDraw = async () => {
|
||||||
|
endPoint.value = undefined
|
||||||
|
startToEndPath.value = undefined
|
||||||
|
|
||||||
|
context.value = canvasElement.value?.getContext('2d') || undefined;
|
||||||
|
const lines = plan.np_array
|
||||||
|
for (let indexY = 0; indexY < lines.length; indexY++) {
|
||||||
|
const line = lines[indexY];
|
||||||
|
for (let indexX = 0; indexX < line.length; indexX++) {
|
||||||
|
const point = line[indexX];
|
||||||
|
if (canvasElement.value && context.value && point > 0) {
|
||||||
|
context.value.fillStyle = 'purple'
|
||||||
|
context.value.fillRect(indexX, indexY, 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (indexY % 4 == 0) {
|
||||||
|
await nextFrame()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const quantum_lines = plan.prepared_array
|
||||||
|
quantum_lines.forEach((line, indexY) => {
|
||||||
|
line.forEach((point, indexX) => {
|
||||||
|
const chunkSize = plan.chunk_size || 8
|
||||||
|
const targetX = indexX * chunkSize
|
||||||
|
const targetY = indexY * chunkSize
|
||||||
|
paths.value.push({
|
||||||
|
path: `M${targetX} ${targetY} ${targetX + chunkSize} ${targetY} ${targetX + chunkSize} ${targetY + chunkSize} ${targetX} ${targetY + chunkSize}Z`,
|
||||||
|
unwalkable: !!point,
|
||||||
|
x: indexX,
|
||||||
|
y: indexY,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
grid.value = new PF.Grid(plan.prepared_array.map(y => y.map(x => x > 0 ? 1 : 0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const findPath = async () => {
|
||||||
|
if (!endPoint.value) return
|
||||||
|
const localPath = finder.findPath(
|
||||||
|
Math.round(startPoint.value.x),
|
||||||
|
Math.round(startPoint.value.y),
|
||||||
|
Math.round(endPoint.value.x),
|
||||||
|
Math.round(endPoint.value.y),
|
||||||
|
(grid.value?.clone() as Grid)
|
||||||
|
);
|
||||||
|
startToEndPath.value = localPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const setPointSvg = (item: PathItem) => {
|
||||||
|
// startToEndPath.value = []
|
||||||
|
endPoint.value = { x: item.x, y: item.y }
|
||||||
|
findPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await floorplan.getData(parseInt(route.params.id as string))
|
||||||
|
newDraw()
|
||||||
|
})
|
||||||
|
const cw = 1920
|
||||||
|
const ch = 800
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="container" style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
|
||||||
|
<canvas ref="canvasElement" :width="cw" :height="ch"></canvas>
|
||||||
|
<svg ref="svgElement" :width="cw" :height="ch" style="position: absolute;">
|
||||||
|
<path v-for="item in paths" :d="item.path" @click="setPointSvg(item)" :class="[
|
||||||
|
{ 'unwalkable': item.unwalkable },
|
||||||
|
{ 'endPoint': (endPoint && item.x == endPoint.x && item.y == endPoint.y) },
|
||||||
|
{ 'startPoint': (startPoint && item.x == startPoint.x && item.y == startPoint.y) },
|
||||||
|
{ 'pathPoint': (startToEndPath && startToEndPath.find((el: number[]) => el[0] == item.x && el[1] == item.y)) },
|
||||||
|
]">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style scoped>
|
||||||
|
svg path {
|
||||||
|
fill: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path:hover {
|
||||||
|
fill: red
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.unwalkable {
|
||||||
|
/* fill: rgba(0, 0, 0, 0.5); */
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.endPoint {
|
||||||
|
fill: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.startPoint {
|
||||||
|
fill: lawngreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path.pathPoint {
|
||||||
|
fill: gold;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -9,12 +9,14 @@ import Home from './components/Home.vue'
|
||||||
import Projects from './components/Projects.vue'
|
import Projects from './components/Projects.vue'
|
||||||
import Game from './components/Game.vue'
|
import Game from './components/Game.vue'
|
||||||
import Floorplan from './components/Floorplan.vue'
|
import Floorplan from './components/Floorplan.vue'
|
||||||
|
import FloorplanItem from './components/FloorplanItem.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', component: Home },
|
{ path: '/', component: Home },
|
||||||
{ path: '/projects', component: Projects },
|
{ path: '/projects', component: Projects },
|
||||||
{ path: '/game', component: Game },
|
{ path: '/game', component: Game },
|
||||||
{ path: '/floorplan', component: Floorplan },
|
{ path: '/floorplan', component: Floorplan },
|
||||||
|
{ path: '/floorplan/:id', component: FloorplanItem },
|
||||||
]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|
|
@ -5,34 +5,60 @@ import { chunks } from '../helpers'
|
||||||
export const useFloorplanStore = defineStore('floorplan', {
|
export const useFloorplanStore = defineStore('floorplan', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
|
items: [] as { id: string, title: string }[],
|
||||||
title: undefined,
|
title: undefined,
|
||||||
chunkSize: 7,
|
|
||||||
np_array: [] as number[][],
|
np_array: [] as number[][],
|
||||||
prepared_array: [] as number[][]
|
prepared_array: [] as number[][],
|
||||||
|
chunk_size: undefined as number | undefined,
|
||||||
|
threshold: undefined as number | undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async getData() {
|
async getList() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${SERVER_URL}/api/floorplan`)
|
const res = await fetch(`${SERVER_URL}/api/floorplan/`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
this.items = data
|
||||||
|
// this.title = data.title
|
||||||
|
// this.np_array = data.np_field
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getData(id: number) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${SERVER_URL}/api/floorplan/${id}`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
})
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
this.title = data.title
|
this.title = data.title
|
||||||
this.np_array = data.np_field
|
this.np_array = data.np_field
|
||||||
this.prepared_array = [...chunks(data.np_field, this.chunkSize)].map(line => {
|
this.chunk_size = data.d_size || 8
|
||||||
|
this.threshold = data.d_border || 20
|
||||||
|
|
||||||
|
this.prepared_array = [...chunks(data.np_field, this.chunk_size as number)].map(line => {
|
||||||
const line_data = [] as any[][]
|
const line_data = [] as any[][]
|
||||||
line.map((item: any) => [...chunks(item, this.chunkSize)]).map((item) => {
|
line.map((item: any) => [...chunks(item, this.chunk_size as number)]).map((item) => {
|
||||||
item.map((one_line, k) => {
|
item.map((one_line, k) => {
|
||||||
if (!line_data[k]) line_data[k] = []
|
if (!line_data[k]) line_data[k] = []
|
||||||
line_data[k].push(...one_line)
|
line_data[k].push(...one_line)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return line_data.map(el => {
|
return line_data.map(el => {
|
||||||
return el.filter(e => e > 0).length > 20 ? 1 : 0
|
return el.filter(e => e > 0).length > (this.threshold as number) ? 1 : 0
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// this.list = []
|
console.log(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -27,5 +27,5 @@ build-backend = "poetry.core.masonry.api"
|
||||||
export_req = "poetry export --without-hashes --format=requirements.txt > back/requirements.txt"
|
export_req = "poetry export --without-hashes --format=requirements.txt > back/requirements.txt"
|
||||||
server = "back/manage.py runserver 0.0.0.0:8000"
|
server = "back/manage.py runserver 0.0.0.0:8000"
|
||||||
front = "cd front && npm run build && npm run preview"
|
front = "cd front && npm run build && npm run preview"
|
||||||
front_dev = "cd front && npm run dev"
|
front_dev = "cd front && npm run dev -- --host"
|
||||||
admin_front_dev = "cd admin_front && npm run dev"
|
admin_front_dev = "cd admin_front && npm run dev"
|
Loading…
Reference in New Issue