scan front
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
CharacterApi,
|
||||
CharacterRuleBookApi,
|
||||
LedgerApi,
|
||||
MarketApi,
|
||||
ProcessingApi,
|
||||
RuleBookApi,
|
||||
TransactionApi
|
||||
@@ -31,3 +32,4 @@ export const characterRuleBookApi = new CharacterRuleBookApi(undefined, mammonUr
|
||||
export const activityApi = new ActivityApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||
export const processingApi = new ProcessingApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||
export const acquisitionApi = new AcquisitionApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||
export const marketApi = new MarketApi(undefined, mammonUrl, mammonAxiosInstance);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import {SliderCheckbox} from '@/components';
|
||||
import {SortableHeader, useSort, VirtualScrollTable} from '@/components/table';
|
||||
import {getHistoryQuartils, MarketType, MarketTypeLabel, TaxInput, useMarketTaxStore} from "@/market";
|
||||
import {BookmarkSlashIcon, ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||
import {formatIsk, percentFormater} from "@/formaters";
|
||||
import {MarketType, MarketTypeLabel} from "@/market";
|
||||
import {ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||
import {useStorage} from '@vueuse/core';
|
||||
import {computed, ref} from 'vue';
|
||||
import {useAcquiredTypesStore} from '../acquisition';
|
||||
import {TrackingResult} from './tracking';
|
||||
import {ScanResult} from './scan';
|
||||
|
||||
type Result = {
|
||||
type: MarketType;
|
||||
@@ -17,25 +18,28 @@ type Result = {
|
||||
q1: number;
|
||||
median: number;
|
||||
q3: number;
|
||||
totalVolume: number;
|
||||
acquisitions: number;
|
||||
profit: number;
|
||||
score: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
items?: TrackingResult[];
|
||||
items?: ScanResult[];
|
||||
infoOnly?: boolean;
|
||||
ignoredColums?: string[] | string;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'buy', type: MarketType, buy: number, sell: number): void;
|
||||
(e: 'remove', type: MarketType): void;
|
||||
}
|
||||
|
||||
const scoreFormater = new Intl.NumberFormat("en-US", {
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
const volumeFormater = new Intl.NumberFormat("en-US", {
|
||||
maximumFractionDigits: 0
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
@@ -44,11 +48,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
defineEmits<Emits>();
|
||||
|
||||
const marketTaxStore = useMarketTaxStore();
|
||||
const acquiredTypesStore = useAcquiredTypesStore();
|
||||
|
||||
const days = useStorage('market-tracking-days', 365);
|
||||
const threshold = useStorage('market-tracking-threshold', 10);
|
||||
const threshold = useStorage('market-scan-threshold', 10);
|
||||
const filter = ref("");
|
||||
const onlyCheap = ref(false);
|
||||
const columnsToIgnore = computed(() => {
|
||||
@@ -62,9 +64,6 @@ const columnsToIgnore = computed(() => {
|
||||
const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() => props.items
|
||||
.filter(r => r.type.name.toLowerCase().includes(filter.value.toLowerCase()))
|
||||
.map(r => {
|
||||
const quartils = getHistoryQuartils(r.history, days.value);
|
||||
const profit = quartils.q1 === 0 || quartils.q3 === 0 ? 0 : marketTaxStore.calculateProfit(quartils.q1, quartils.q3);
|
||||
const score = profit <= 0 ? 0 : Math.sqrt((Math.pow(quartils.totalVolume, 1.1) * Math.pow(quartils.q1, 1.2) * Math.pow(profit, 0.5) * Math.pow(Math.max(1, r.orderCount), -0.7)) / days.value);
|
||||
const acquisitions = columnsToIgnore.value.includes('acquisitions') ? 0 : acquiredTypesStore.acquiredTypes
|
||||
.filter(t => t.type === r.type.id)
|
||||
.reduce((a, b) => a + b.remaining, 0);
|
||||
@@ -75,12 +74,13 @@ const { sortedArray, headerProps, showColumn } = useSort<Result>(computed(() =>
|
||||
name: r.type.name,
|
||||
buy: r.buy,
|
||||
sell: r.sell,
|
||||
q1: quartils.q1,
|
||||
median: quartils.median,
|
||||
q3: quartils.q3,
|
||||
q1: r.q1,
|
||||
median: r.median,
|
||||
q3: r.q3,
|
||||
totalVolume: r.totalVolume,
|
||||
acquisitions,
|
||||
profit,
|
||||
score
|
||||
profit: r.profit,
|
||||
score: r.score
|
||||
};
|
||||
}).filter(r => !onlyCheap.value || (r.buy <= r.q1 && r.profit >= (threshold.value / 100)))), {
|
||||
defaultSortKey: 'score',
|
||||
@@ -104,15 +104,10 @@ const getLineColor = (result: Result) => {
|
||||
<template>
|
||||
<div v-if="!infoOnly" class="flex mb-2 mt-4">
|
||||
<div class="flex justify-self-end ms-auto">
|
||||
<TaxInput />
|
||||
<div class="end">
|
||||
<span>Profit Threshold: </span>
|
||||
<input type="number" min="0" max="1000" step="1" v-model="threshold" />
|
||||
</div>
|
||||
<div class="end">
|
||||
<span>Days: </span>
|
||||
<input type="number" min="1" max="365" step="1" v-model="days" />
|
||||
</div>
|
||||
<div class="end flex">
|
||||
<SliderCheckbox class="me-1" v-model="onlyCheap" /> Show only cheap items
|
||||
</div>
|
||||
@@ -132,6 +127,7 @@ const getLineColor = (result: Result) => {
|
||||
<SortableHeader v-bind="headerProps" sortKey="q1">Q1</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="median">Median</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="q3">Q3</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="totalVolume">Volume</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="profit">Profit</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="score">Score</SortableHeader>
|
||||
<SortableHeader v-bind="headerProps" sortKey="acquisitions">Acquisitions</SortableHeader>
|
||||
@@ -148,12 +144,12 @@ const getLineColor = (result: Result) => {
|
||||
<td v-if="showColumn('q1')" class="text-right">{{ formatIsk(r.data.q1) }}</td>
|
||||
<td v-if="showColumn('median')" class="text-right">{{ formatIsk(r.data.median) }}</td>
|
||||
<td v-if="showColumn('q3')" class="text-right">{{ formatIsk(r.data.q3) }}</td>
|
||||
<td v-if="showColumn('totalVolume')" class="text-right">{{ volumeFormater.format(r.data.totalVolume) }}</td>
|
||||
<td v-if="showColumn('profit')" class="text-right">{{ percentFormater.format(r.data.profit) }}</td>
|
||||
<td v-if="showColumn('score')" class="text-right">{{ scoreFormater.format(r.data.score) }}</td>
|
||||
<td v-if="showColumn('acquisitions')" class="text-right">{{ r.data.acquisitions }}</td>
|
||||
<td v-if="showColumn('buttons')" class="text-right">
|
||||
<button class="btn-icon me-1" title="Add acquisitions" @click="$emit('buy', r.data.type, r.data.buy, r.data.sell)"><ShoppingCartIcon /></button>
|
||||
<button class="btn-icon me-1" title="Untrack" @click="$emit('remove', r.data.type)"><BookmarkSlashIcon /></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -171,4 +167,4 @@ const getLineColor = (result: Result) => {
|
||||
div.end {
|
||||
@apply justify-self-end ms-2;
|
||||
}
|
||||
</style>../history/HistoryQuartils
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './scan';
|
||||
|
||||
export { default as ScanResultTable } from './ScanResultTable.vue';
|
||||
@@ -0,0 +1,53 @@
|
||||
import { getHistory, getHistoryQuartils, HistoryQuartils, MarketType, MarketTypePrice } from "@/market";
|
||||
import { MarketScanResponse } from "@/generated/mammon";
|
||||
|
||||
export type ScanResult = {
|
||||
type: MarketType;
|
||||
buy: number;
|
||||
sell: number;
|
||||
q1: number;
|
||||
median: number;
|
||||
q3: number;
|
||||
totalVolume: number;
|
||||
profit: number;
|
||||
score: number;
|
||||
}
|
||||
|
||||
// Mirrors mammon's MarketScoreCalculator so the client-side path matches the backend scan.
|
||||
export const calculateScore = (quartils: HistoryQuartils, profit: number, orderCount: number, days: number): number => {
|
||||
if (profit <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.sqrt((Math.pow(quartils.totalVolume, 1.1) * Math.pow(quartils.q1, 1.2) * Math.pow(profit, 0.5) * Math.pow(Math.max(1, orderCount), -0.7)) / days);
|
||||
}
|
||||
|
||||
export const toScanResult = (res: MarketScanResponse, type: MarketType, price: MarketTypePrice): ScanResult => ({
|
||||
type,
|
||||
buy: price.buy,
|
||||
sell: price.sell,
|
||||
q1: res.q1,
|
||||
median: res.median,
|
||||
q3: res.q3,
|
||||
totalVolume: res.totalVolume,
|
||||
profit: res.profit,
|
||||
score: res.score,
|
||||
});
|
||||
|
||||
// Client-side scan result for a single type (used where the scan endpoint can't be queried per-type).
|
||||
export const buildScanResult = async (price: MarketTypePrice, days: number, calculateProfit: (buy: number, sell: number) => number): Promise<ScanResult> => {
|
||||
const history = await getHistory(price.type.id);
|
||||
const quartils = getHistoryQuartils(history, days);
|
||||
const profit = quartils.q1 === 0 || quartils.q3 === 0 ? 0 : calculateProfit(quartils.q1, quartils.q3);
|
||||
|
||||
return {
|
||||
type: price.type,
|
||||
buy: price.buy,
|
||||
sell: price.sell,
|
||||
q1: quartils.q1,
|
||||
median: quartils.median,
|
||||
q3: quartils.q3,
|
||||
totalVolume: quartils.totalVolume,
|
||||
profit,
|
||||
score: calculateScore(quartils, profit, price.orderCount, days),
|
||||
};
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './tracking';
|
||||
|
||||
export { default as TrackingResultTable } from './TrackingResultTable.vue';
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { EsiMarketOrderHistory, getHistory, MarketType, MarketTypePrice } from "@/market";
|
||||
import log from "loglevel";
|
||||
import { defineStore } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
export type TrackingResult = {
|
||||
type: MarketType;
|
||||
history: EsiMarketOrderHistory[];
|
||||
buy: number,
|
||||
sell: number,
|
||||
orderCount: number,
|
||||
}
|
||||
|
||||
const endpoint = '/api/types_tracking/';
|
||||
|
||||
export const useMarketTrackingStore = defineStore('marketTracking', () => {
|
||||
const trackedTypes = ref<any[]>([]); // TODO
|
||||
|
||||
const types = computed(() => trackedTypes.value.map(item => item.type) ?? []);
|
||||
const addType = async (type: number) => {
|
||||
const found = trackedTypes.value.find(item => item.type === type);
|
||||
|
||||
if (!found) {
|
||||
log.info(`Tracking type ${type}`);
|
||||
}
|
||||
}
|
||||
const removeType = async (type: number) => {
|
||||
const found = trackedTypes.value.find(item => item.type === type);
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
trackedTypes.value = trackedTypes.value.filter(t => t.id !== found.id);
|
||||
}
|
||||
|
||||
return { types, addType, removeType };
|
||||
});
|
||||
|
||||
export const createResult = async (id: number, price: MarketTypePrice): Promise<TrackingResult> => ({ history: await getHistory(id), ...price });
|
||||
@@ -9,8 +9,8 @@ import {routeNames} from '@/routes';
|
||||
<RouterLink :to="{name: routeNames.marketTypes}" class="tab">
|
||||
<span>Item Info</span>
|
||||
</RouterLink>
|
||||
<RouterLink to="/market/tracking" class="tab">
|
||||
<span>Tracking</span>
|
||||
<RouterLink to="/market/scan" class="tab">
|
||||
<span>Scan</span>
|
||||
</RouterLink>
|
||||
<RouterLink to="/market/acquisitions" class="tab">
|
||||
<span>Acquisitions</span>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import { getMarketTypes, TaxInput, useApraisalStore, useMarketTaxStore } from "@/market";
|
||||
import { BuyModal } from '@/market/acquisition';
|
||||
import { ScanResult, ScanResultTable, toScanResult } from '@/market/scan';
|
||||
import { marketApi } from "@/mammon";
|
||||
import { useStorage } from "@vueuse/core";
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const buyModal = ref<typeof BuyModal>();
|
||||
|
||||
const apraisalStore = useApraisalStore();
|
||||
const marketTaxStore = useMarketTaxStore();
|
||||
const days = useStorage('market-scan-days', 365);
|
||||
const items = ref<ScanResult[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const scan = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await marketApi.scanMarket(
|
||||
String(days.value),
|
||||
String(marketTaxStore.brokerFee / 100),
|
||||
String(marketTaxStore.scc / 100)
|
||||
);
|
||||
const types = await getMarketTypes(data.map(r => r.marketTypeId));
|
||||
const prices = await apraisalStore.getPrices(types);
|
||||
|
||||
items.value = data.flatMap(r => {
|
||||
const type = types.find(t => t.id === r.marketTypeId);
|
||||
const price = prices.find(p => p.type.id === r.marketTypeId);
|
||||
|
||||
return type && price ? [toScanResult(r, type, price)] : [];
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch([days, () => marketTaxStore.brokerFee, () => marketTaxStore.scc], scan, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex mb-2 mt-4">
|
||||
<div class="flex justify-self-end ms-auto">
|
||||
<TaxInput />
|
||||
<div class="end">
|
||||
<span>Days: </span>
|
||||
<input type="number" min="1" max="365" step="1" v-model="days" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div v-if="loading" class="text-center mt-4">
|
||||
<span>Scanning market…</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<ScanResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" />
|
||||
<BuyModal ref="buyModal" />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@reference "@/style.css";
|
||||
div.end {
|
||||
@apply justify-self-end ms-2;
|
||||
}
|
||||
</style>
|
||||
@@ -1,82 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Modal, ProgressBar } from "@/components";
|
||||
import { MarketType, MarketTypeInput, MarketTypePrice, getHistory, getMarketTypes, useApraisalStore } from "@/market";
|
||||
import { BuyModal } from '@/market/acquisition';
|
||||
import { TrackingResult, TrackingResultTable, createResult, useMarketTrackingStore } from '@/market/tracking';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
|
||||
const buyModal = ref<typeof BuyModal>();
|
||||
|
||||
const item = ref<MarketType>();
|
||||
|
||||
const apraisalStore = useApraisalStore();
|
||||
const marketTrackingStore = useMarketTrackingStore();
|
||||
const items = ref<TrackingResult[]>([]);
|
||||
const addOrRelaod = async (type: MarketType) => {
|
||||
const typeID = type.id;
|
||||
const [history, price] = await Promise.all([
|
||||
getHistory(typeID),
|
||||
apraisalStore.getPrice(type)
|
||||
]);
|
||||
const itm = {
|
||||
type,
|
||||
history,
|
||||
buy: price.buy,
|
||||
sell: price.sell,
|
||||
orderCount: price.orderCount
|
||||
};
|
||||
|
||||
if (items.value.some(i => i.type.id === typeID)) {
|
||||
items.value = items.value.map(i => i.type.id === typeID ? itm : i);
|
||||
} else {
|
||||
items.value = [ ...items.value, itm];
|
||||
marketTrackingStore.addType(typeID);
|
||||
}
|
||||
}
|
||||
const addItem = async () => {
|
||||
if (!item.value) {
|
||||
// TODO error
|
||||
return;
|
||||
}
|
||||
|
||||
addOrRelaod(item.value);
|
||||
item.value = undefined;
|
||||
}
|
||||
const removeItem = (type: MarketType) => {
|
||||
items.value = items.value.filter(i => i.type.id !== type.id);
|
||||
marketTrackingStore.removeType(type.id);
|
||||
}
|
||||
|
||||
watch(() => marketTrackingStore.types, async t => {
|
||||
const typesToLoad = t.filter(t => !items.value.some(i => i.type.id === t));
|
||||
|
||||
if (typesToLoad.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prices = await apraisalStore.getPrices(await getMarketTypes(typesToLoad));
|
||||
|
||||
typesToLoad.forEach(async i => items.value.push(await createResult(i, prices.find(p => p.type.id === i) as MarketTypePrice)));
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid mb-2 mt-4">
|
||||
<div class="w-auto flex">
|
||||
<span>Item: </span>
|
||||
<MarketTypeInput class="ms-2" v-model="item" @submit="addItem"/>
|
||||
<button class="justify-self-end ms-2" @click="addItem">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="items.length > 0">
|
||||
<hr />
|
||||
<TrackingResultTable :items="items" @buy="(type, buy, sell) => buyModal?.open(type, { 'Buy': buy, 'Sell': sell })" @remove="removeItem" />
|
||||
<BuyModal ref="buyModal" />
|
||||
<Modal :open="items.length > 0 && items.length < marketTrackingStore.types.length">
|
||||
<div class="ms-auto me-auto mb-2 w-96">
|
||||
<ProgressBar :value="items.length" :total="marketTrackingStore.types.length" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
</template>
|
||||
@@ -1,14 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import {ClipboardButton} from '@/components';
|
||||
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore} from "@/market";
|
||||
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore, useMarketTaxStore} from "@/market";
|
||||
import {AcquisitionResultTable, BuyModal, useAcquiredTypesStore} from '@/market/acquisition';
|
||||
import {createResult, TrackingResultTable, useMarketTrackingStore} from '@/market/tracking';
|
||||
import {BookmarkIcon, BookmarkSlashIcon, ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||
import {buildScanResult, ScanResultTable} from '@/market/scan';
|
||||
import {ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||
import log from "loglevel";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {routeNames} from "@/routes";
|
||||
import {computedAsync} from "@vueuse/core";
|
||||
import {computedAsync, useStorage} from "@vueuse/core";
|
||||
|
||||
const buyModal = ref<typeof BuyModal>();
|
||||
|
||||
@@ -18,12 +18,12 @@ const item = ref<MarketType>();
|
||||
const inputItem = ref<MarketType>();
|
||||
|
||||
const apraisalStore = useApraisalStore();
|
||||
const marketTaxStore = useMarketTaxStore();
|
||||
const days = useStorage('market-scan-days', 365);
|
||||
const price = computedAsync(() => item.value ? apraisalStore.getPrice(item.value) : undefined);
|
||||
const marketTrackingStore = useMarketTrackingStore();
|
||||
const result = computedAsync(async () => item.value && price.value ? await createResult(item.value?.id, price.value) : undefined);
|
||||
const result = computedAsync(async () => price.value ? await buildScanResult(price.value, days.value, marketTaxStore.calculateProfit) : undefined);
|
||||
const acquiredTypesStore = useAcquiredTypesStore();
|
||||
|
||||
const isTracked = computed(() => item.value ? marketTrackingStore.types.includes(item.value.id) : false);
|
||||
const acquisitions = computed(() => {
|
||||
const p = price.value;
|
||||
|
||||
@@ -36,17 +36,6 @@ const acquisitions = computed(() => {
|
||||
sell: p.sell
|
||||
}));
|
||||
});
|
||||
const toogleTracking = () => {
|
||||
if (!item.value) {
|
||||
return;
|
||||
}
|
||||
if (isTracked.value) {
|
||||
marketTrackingStore.removeType(item.value.id);
|
||||
} else {
|
||||
marketTrackingStore.addType(item.value.id);
|
||||
}
|
||||
}
|
||||
|
||||
const view = () => {
|
||||
if (!inputItem.value) {
|
||||
return;
|
||||
@@ -93,10 +82,6 @@ watch(useRoute(), async route => {
|
||||
<div class="ms-auto">
|
||||
<ClipboardButton class="ms-1" :value="item.name" />
|
||||
<button v-if="price" class="btn-icon ms-1" title="Add acquisitions" @click="buyModal?.open(item, { 'Buy': price.buy, 'Sell': price.sell })"><ShoppingCartIcon /></button>
|
||||
<button class="btn-icon ms-1" :title="isTracked ? 'Untrack' : 'Track'" @click="toogleTracking">
|
||||
<BookmarkSlashIcon v-if="isTracked" />
|
||||
<BookmarkIcon v-else />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="item.description" class="text-sm">{{ item.description }}</p>
|
||||
@@ -104,7 +89,7 @@ watch(useRoute(), async route => {
|
||||
</div>
|
||||
<div v-if="result" class="mb-4">
|
||||
<span>Market Info:</span>
|
||||
<TrackingResultTable :items="[result]" infoOnly :ignoredColums="['name', 'acquisitions']" />
|
||||
<ScanResultTable :items="[result]" infoOnly :ignoredColums="['name', 'acquisitions']" />
|
||||
</div>
|
||||
<div v-if="acquisitions && acquisitions.length > 0">
|
||||
<span>Acquisitions:</span>
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ export const routes: RouteRecordRaw[] = [
|
||||
{path: '/market', component: () => import('@/pages/Market.vue'), children: [
|
||||
{path: '', redirect: {name: routeNames.marketTypes}},
|
||||
{path: 'types/:type?', name: routeNames.marketTypes, component: () => import('@/pages/market/TypeInfo.vue')},
|
||||
{path: 'tracking', component: () => import('@/pages/market/Tracking.vue')},
|
||||
{path: 'scan', component: () => import('@/pages/market/Scan.vue')},
|
||||
{path: 'acquisitions', component: () => import('@/pages/market/Acquisitions.vue')},
|
||||
]},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user