Multiworld stuff

This commit is contained in:
2025-03-13 21:51:34 -05:00
parent c148272d38
commit 054fc9231c
12 changed files with 465 additions and 164 deletions

93
src/components/Multi.vue Normal file
View File

@@ -0,0 +1,93 @@
<script>
import { defineComponent } from "vue";
import { Base64 } from "js-base64";
import axios from "axios";
export default defineComponent({
data() {
return {
multidata: null,
worlds: {},
error: null,
};
},
props: {
id: "",
},
async mounted() {
document.title = `ALttPR Multiworld: ${this.id}`;
await this.fetchMulti();
},
computed: {
permalink() {
return `/multi/${this.id}`;
},
},
methods: {
async fetchMulti() {
await axios.get(`/multi/${this.id}`)
.then(response => {
if (response && response.data && response.data.multidata) {
const multi = response.data;
this.multidata = Base64.toUint8Array(multi.multidata);
this.worlds = multi.worlds;
} else {
this.error = "Error loading multiworld.";
}
})
.catch(error => {
if (error.response?.status == 409) {
// still generating, try again
setTimeout(this.fetchMulti.bind(this), 10000);
} else {
this.error = "Multiworld not found. :(";
}
});
},
async downloadMultidata() {
const blob = new Blob([this.multidata], { type: 'octet/stream' });
const link = document.getElementById('downloader');
link.href = URL.createObjectURL(blob);
link.download = `GK_${this.id}_multidata`;
link.click();
},
}
});
</script>
<template>
<div class="mw-30">
<div v-if="multidata" class="card content-div m-3">
<div class="card-header">
Permalink: <a :href="permalink">{{ permalink }}</a>
</div>
<ul class="list-group list-group-flush">
<li v-for="world in worlds" class="list-group-item">
{{ world.name }}: <a :href="`/seed/${world.id}`">/seed/{{ world.id }}</a>
</li>
<li class="list-group-item">
<button type="submit" class="btn btn-primary submit-btn" :disabled="!multidata" @click="downloadMultidata">
Download Multidata!
</button>
<a id="downloader" style="display: none;" />
</li>
</ul>
</div>
<div v-else-if="error" class="error-message">
{{ error }}
</div>
<div v-else>
<img class="center" src="/bludormspinbig.gif" />
</div>
</div>
</template>
<style scoped>
.error-message {
text-align: center;
font-size: xx-large;
font-weight: bold;
margin-top: 3rem;
}
</style>

View File

@@ -69,6 +69,9 @@ export default defineComponent({
},
settingsMatch(newSettings, preset) {
for (const settingName of Object.keys(newSettings)) {
if (settingName == "player_name") {
continue;
}
if (preset[settingName] != newSettings[settingName]) {
return false;
}

View File

@@ -36,6 +36,7 @@ export default defineComponent({
patch: null,
error: null,
settings: null,
multi: null,
};
},
props: {
@@ -63,6 +64,7 @@ export default defineComponent({
dataLoaded(patch, seedData) {
this.patch = patch;
this.settings = seedData.settings;
this.multi = seedData.parent;
},
async fetchSeed() {
await axios.get(`/seed/${this.id}`)
@@ -175,7 +177,7 @@ export default defineComponent({
</li>
<li v-if="settings" class="list-group-item">
<div class="mb-1">
<SeedSettings :settings="settings" />
<SeedSettings :settings="settings" :multi="multi" />
</div>
</li>
<li class="list-group-item">

View File

@@ -7,6 +7,7 @@ import mustache from "mustache";
export default defineComponent({
props: {
settings: {},
multi: null,
},
computed: {
settingsDisplay() {
@@ -29,7 +30,14 @@ export default defineComponent({
|| (this.settings.maps && this.settings.maps != "dungeon")
|| (this.settings.compasses && this.settings.compasses != "dungeon")
|| (this.settings.prize_shuffle && this.settings.prize_shuffle != "vanilla");
}
},
multilink() {
if (this.multi) {
return `/multi/${this.multi}`;
} else {
return null;
}
},
},
methods: {
}
@@ -37,6 +45,10 @@ export default defineComponent({
</script>
<template>
<div v-if="multi">
Part of Multiworld: <a :href="multilink">{{ multilink }}</a>
<hr class="mt-2 mb-2" />
</div>
<div v-if="settings.randomizer && settingsDisplay.randomizer[settings.randomizer]">
{{ settingsDisplay.randomizer[settings.randomizer] }}
<hr class="mt-2 mb-2" />

View File

@@ -19,6 +19,7 @@ export default defineComponent({
name: null,
color: "primary",
generator: null,
prefix: null,
},
watch: {
async modelValue(newValue, oldValue) {
@@ -44,7 +45,7 @@ export default defineComponent({
},
},
async mounted() {
this.selected = await localforage.getItem(`setting_${this.name}`) ?? this.settings.default;
this.selected = await localforage.getItem(`${this.prefix}setting_${this.name}`) ?? this.settings.default;
this.change();
},
methods: {
@@ -53,7 +54,7 @@ export default defineComponent({
await this.updateLocalForage();
},
async updateLocalForage() {
await localforage.setItem(`setting_${this.name}`, this.selected);
await localforage.setItem(`${this.prefix}setting_${this.name}`, this.selected);
},
},
});
@@ -64,9 +65,9 @@ export default defineComponent({
<div class="nav nav-pills nav-fill mt-1 mb-1">
<template v-for="value of settings.order ?? Object.keys(settings.values)">
<template v-if="settings.values[value]">
<input type="radio" class="btn-check" :name="name" :id="`${name}_${value}`"
<input type="radio" class="btn-check" :name="`${prefix}${name}`" :id="`${prefix}${name}_${value}`"
autocomplete="off" :value="value" v-model="selected" @change="change" />
<label :class="`btn btn-outline-${color} nav-item m-1`" :for="`${name}_${value}`">
<label :class="`btn btn-outline-${color} nav-item m-1`" :for="`${prefix}${name}_${value}`">
{{ settings.values[value].display }}
</label>
</template>

View File

@@ -0,0 +1,209 @@
<script>
import { defineComponent } from "vue";
import axios from "axios";
import { Modal } from "bootstrap";
import AccordionItem from "@/components/AccordionItem.vue";
import PresetPicker from "@/components/PresetPicker.vue";
import SettingPicker from "@/components/SettingPicker.vue";
export default defineComponent({
components: {
AccordionItem,
PresetPicker,
SettingPicker,
},
emits: [
'update:modelValue'
],
props: {
modelValue: {},
prefix: null,
},
data() {
return {
set: {},
existingLocalPresets: [],
newPresetName: "",
replacePreset: null,
modal: null,
};
},
mounted() {
this.modal = new Modal(document.getElementById("savePresetModal"), {});
},
watch: {
set: {
handler(newValue, oldValue) {
this.$refs.preset.settingChanged(newValue);
this.$emit("update:modelValue", this.set);
},
deep: true,
}
},
methods: {
async generate(race) {
const settings = {
randomizer: "base",
race: race ? "race" : "normal"
};
for (const setting of Object.keys(this.set)) {
settings[setting] = this.set[setting];
}
await axios.post("/generate", settings)
.then(response => {
const id = response.data;
this.$router.push(`/seed/${id}`);
})
.catch(error => {
console.log(error);
});
},
presetSelected(preset) {
for (const setting of Object.keys(this.set)) {
if (preset[setting] != undefined) {
this.set[setting] = preset[setting];
}
}
},
savePreset(names) {
this.newPresetName = "";
this.existingLocalPresets = names;
this.modal.show();
},
async modalSavePreset() {
const preset = JSON.parse(JSON.stringify(this.set));
preset.display = this.newPresetName;
if (this.replacePreset != null) {
preset.display = this.existingLocalPresets[this.replacePreset];
}
await this.$refs.preset.savePreset(this.replacePreset, preset);
this.modal.hide();
},
},
});
</script>
<template>
<div class="modal" tabindex="-1" id="savePresetModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Save Preset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="input-group mb-3">
<select v-model="replacePreset" class="form-select">
<option :value="null" selected>New</option>
<optgroup v-if="existingLocalPresets.length" label="Overwrite Preset:">
<template v-for="(preset, idx) of existingLocalPresets">
<option :value="idx">
{{ preset }}
</option>
</template>
</optgroup>
</select>
</div>
<div class="input-group mb-3" v-if="replacePreset == null">
<label class="input-group-text" for="new-preset-name">
Name
</label>
<input type="text" class="form-control" placeholder="Display Name"
id="new-preset-name" v-model="newPresetName" autocomplete="off" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancel
</button>
<button :disabled="replacePreset == null && newPresetName.length == 0" type="button"
class="btn btn-primary" @click="modalSavePreset">
Save Preset
</button>
</div>
</div>
</div>
</div>
<div id="settings" class="accordion accordion-flush">
<AccordionItem>
<PresetPicker ref="preset" generator="base" @selected="presetSelected"
@save="savePreset" />
</AccordionItem>
<AccordionItem :expanded="true">
<template #header>
<b>World:</b>
</template>
<SettingPicker color="primary" v-model="set.mode" name="mode" generator="base" :prefix="prefix" />
<SettingPicker color="primary" v-model="set.weapons" name="weapons" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="true">
<template #header>
<b>Goal:</b>
</template>
<SettingPicker color="success" v-model="set.goal" name="goal" generator="base" :prefix="prefix" />
<SettingPicker color="success" v-if="['ganon', 'fast_ganon'].includes(this.set.goal)"
v-model="set.crystals_ganon" name="crystals_ganon" generator="base" :prefix="prefix" />
<SettingPicker color="success" v-model="set.crystals_gt" name="crystals_gt" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Entrance Shuffle:</b>
</template>
<SettingPicker color="warning" v-model="set.entrance_shuffle" name="entrance_shuffle" generator="base" :prefix="prefix" />
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
v-model="set.skull_woods" name="skull_woods" generator="base" :prefix="prefix" />
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
v-model="set.linked_drops" name="linked_drops" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Dungeon Shuffle:</b>
</template>
<SettingPicker color="danger" v-model="set.door_shuffle" name="door_shuffle" generator="base" />
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
v-model="set.door_lobbies" name="door_lobbies" generator="base" :prefix="prefix" />
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
v-model="set.door_type_mode" name="door_type_mode" generator="base" :prefix="prefix" />
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
v-model="set.trap_door_mode" name="trap_door_mode" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Enemizer:</b>
</template>
<SettingPicker color="success" v-model="set.boss_shuffle" name="boss_shuffle" generator="base" :prefix="prefix" />
<SettingPicker color="success" v-model="set.enemy_shuffle" name="enemy_shuffle" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Dungeon Item Shuffle:</b>
</template>
<SettingPicker color="warning" v-model="set.small_keys" name="small_keys" generator="base" :prefix="prefix" />
<SettingPicker color="warning" v-model="set.big_keys" name="big_keys" generator="base" :prefix="prefix" />
<SettingPicker color="warning" v-model="set.maps" name="maps" generator="base" :prefix="prefix" />
<SettingPicker color="warning" v-model="set.compasses" name="compasses" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Other Pool Settings:</b>
</template>
<SettingPicker color="danger" v-model="set.shop_shuffle" name="shop_shuffle" generator="base" :prefix="prefix" />
<SettingPicker color="danger" v-model="set.drop_shuffle" name="drop_shuffle" generator="base" :prefix="prefix" />
<SettingPicker color="danger" v-model="set.pottery" name="pottery" generator="base" :prefix="prefix" />
<SettingPicker color="danger" v-model="set.prize_shuffle" name="prize_shuffle" generator="base" :prefix="prefix" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Item Settings:</b>
</template>
<SettingPicker color="primary" v-model="set.boots" name="boots" generator="base" :prefix="prefix" />
<SettingPicker color="primary" v-model="set.flute" name="flute" generator="base" :prefix="prefix" />
<SettingPicker color="primary" v-model="set.dark_rooms" name="dark_rooms" generator="base" :prefix="prefix" />
<SettingPicker color="primary" v-model="set.bombs" name="bombs" generator="base" :prefix="prefix" />
<SettingPicker color="primary" v-model="set.book" name="book" generator="base" :prefix="prefix" />
</AccordionItem>
</div>
</template>

View File

@@ -5,6 +5,7 @@ import router from "./router";
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap-icons/font/bootstrap-icons.css";
import "vue-select/dist/vue-select.css";
import "bootstrap/js/dist/tab.js";
import "bootstrap";
import "./assets/main.css";

View File

@@ -1,6 +1,8 @@
import { createRouter, createWebHistory } from "vue-router";
import SeedView from "@/views/SeedView.vue";
import MultiView from "@/views/MultiView.vue";
import GenerateView from "@/views/GenerateView.vue";
import GenerateMulti from "@/views/GenerateMulti.vue";
import GenerateApr2025View from "@/views/GenerateApr2025View.vue";
const router = createRouter({
@@ -12,6 +14,12 @@ const router = createRouter({
component: GenerateView,
alias: '/',
},
{
path: '/multiworld',
name: 'multiworld',
component: GenerateMulti,
alias: '/multi',
},
{
path: '/generate/apr2025',
component: GenerateApr2025View,
@@ -21,6 +29,11 @@ const router = createRouter({
name: 'seed',
component: SeedView,
},
{
path: '/multi/:id',
name: 'multi',
component: MultiView,
},
],
});

106
src/views/GenerateMulti.vue Normal file
View File

@@ -0,0 +1,106 @@
<script>
import { defineComponent } from "vue";
import axios from "axios";
import localforage from "localforage";
import { Modal } from "bootstrap";
import SettingsPage from "@/components/SettingsPage.vue";
export default defineComponent({
components: {
SettingsPage,
},
data() {
return {
worlds: [{}, {}, {}],
worldCount: 3,
};
},
async mounted() {
document.title = "ALttPR Multiworld"
await new Promise(r => setTimeout(r, 100));
for (var i = 0; i < this.worldCount; i++) {
this.worlds[i].player_name = await localforage.getItem(`world_${i + 1}_setting_player_name`);
}
},
methods: {
async playerNameUpdated(num) {
await localforage.setItem(`world_${num + 1}_setting_player_name`, this.worlds[num].player_name);
},
async generate(race) {
const settings = [];
for (var i = 0; i < this.worldCount; i++) {
const world = {
randomizer: "base",
race: race ? "race" : "normal"
};
for (const setting of Object.keys(this.worlds[i])) {
world[setting] = this.worlds[i][setting];
}
if (!world.player_name) {
world.player_name = `Player ${i + 1}`;
}
settings.push(world);
}
await axios.post("/multiworld", settings)
.then(response => {
const id = response.data;
this.$router.push(`/multi/${id}`);
})
.catch(error => {
console.log(error);
});
},
},
});
</script>
<template>
<div class="card content-div m-3">
<div class="card-header">
Generate Multiworld
</div>
<ul class="nav nav-tabs">
<li v-for="(n, idx) in worldCount" class="nav-item" role="presentation">
<button class="nav-link" :class="{active: idx == 0}" data-bs-toggle="tab"
:data-bs-target="`#world_page_${n}`" type="button" role="tab">
{{ worlds[idx]?.player_name ?? n }}
</button>
</li>
</ul>
<div class="tab-content">
<template v-for="(n, idx) in worldCount">
<div :id="`world_page_${n}`" class="tab-pane fade"
:class="{show: idx == 0, active: idx == 0}" role="tabpanel">
<div class="input-group ms-3 me-3 mt-3" style="width: inherit;">
<label class="input-group-text" :for="`player_${n}_name`">
Player {{ n }} Name:
</label>
<input type="text" class="form-control" :placeholder="`Player ${n}`"
id="`player_${n}_name`" v-model="worlds[idx].player_name"
@change="playerNameUpdated(idx)" />
</div>
<div id="settings" class="accordion accordion-flush">
<SettingsPage v-model="worlds[idx]" :prefix="`world_${n}_`" />
</div>
</div>
</template>
</div>
<div class="card-footer">
<div class="nav nav-pills nav-fill" role="group">
<button type="button" class="m-1 nav-item btn btn-outline-danger"
@click="generate(true);">
Generate Race ROM
</button>
<button type="button" class="m-1 nav-item btn btn-outline-primary"
@click="generate(false);">
Generate ROM
</button>
</div>
</div>
</div>
</template>

View File

@@ -4,36 +4,19 @@ import { defineComponent } from "vue";
import axios from "axios";
import { Modal } from "bootstrap";
import AccordionItem from "@/components/AccordionItem.vue";
import PresetPicker from "@/components/PresetPicker.vue";
import SettingPicker from "@/components/SettingPicker.vue";
import SettingsPage from "@/components/SettingsPage.vue";
export default defineComponent({
components: {
AccordionItem,
PresetPicker,
SettingPicker,
SettingsPage,
},
data() {
return {
set: {},
existingLocalPresets: [],
newPresetName: "",
replacePreset: null,
modal: null,
};
},
mounted() {
document.title = "ALttPRandomizer"
this.modal = new Modal(document.getElementById("savePresetModal"), {});
},
watch: {
set: {
handler(newValue, oldValue) {
this.$refs.preset.settingChanged(newValue);
},
deep: true,
}
},
methods: {
async generate(race) {
@@ -53,154 +36,17 @@ export default defineComponent({
console.log(error);
});
},
presetSelected(preset) {
for (const setting of Object.keys(this.set)) {
if (preset[setting] != undefined) {
this.set[setting] = preset[setting];
}
}
},
savePreset(names) {
this.newPresetName = "";
this.existingLocalPresets = names;
this.modal.show();
},
async modalSavePreset() {
const preset = JSON.parse(JSON.stringify(this.set));
preset.display = this.newPresetName;
if (this.replacePreset != null) {
preset.display = this.existingLocalPresets[this.replacePreset];
}
await this.$refs.preset.savePreset(this.replacePreset, preset);
this.modal.hide();
},
},
});
</script>
<template>
<div class="modal" tabindex="-1" id="savePresetModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Save Preset</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="input-group mb-3">
<select v-model="replacePreset" class="form-select">
<option :value="null" selected>New</option>
<optgroup v-if="existingLocalPresets.length" label="Overwrite Preset:">
<template v-for="(preset, idx) of existingLocalPresets">
<option :value="idx">
{{ preset }}
</option>
</template>
</optgroup>
</select>
</div>
<div class="input-group mb-3" v-if="replacePreset == null">
<label class="input-group-text" for="new-preset-name">
Name
</label>
<input type="text" class="form-control" placeholder="Display Name"
id="new-preset-name" v-model="newPresetName" autocomplete="off" />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Cancel
</button>
<button :disabled="replacePreset == null && newPresetName.length == 0" type="button"
class="btn btn-primary" @click="modalSavePreset">
Save Preset
</button>
</div>
</div>
</div>
</div>
<div class="card content-div m-3">
<div class="card-header">
Generate Seed
</div>
<div id="settings" class="accordion accordion-flush">
<AccordionItem>
<PresetPicker ref="preset" generator="base" @selected="presetSelected"
@save="savePreset" />
</AccordionItem>
<AccordionItem :expanded="true">
<template #header>
<b>World:</b>
</template>
<SettingPicker color="primary" v-model="set.mode" name="mode" generator="base" />
<SettingPicker color="primary" v-model="set.weapons" name="weapons" generator="base" />
</AccordionItem>
<AccordionItem :expanded="true">
<template #header>
<b>Goal:</b>
</template>
<SettingPicker color="success" v-model="set.goal" name="goal" generator="base" />
<SettingPicker color="success" v-if="['ganon', 'fast_ganon'].includes(this.set.goal)"
v-model="set.crystals_ganon" name="crystals_ganon" generator="base" />
<SettingPicker color="success" v-model="set.crystals_gt" name="crystals_gt" generator="base" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Entrance Shuffle:</b>
</template>
<SettingPicker color="warning" v-model="set.entrance_shuffle" name="entrance_shuffle" generator="base" />
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
v-model="set.skull_woods" name="skull_woods" generator="base" />
<SettingPicker color="warning" v-if="this.set.entrance_shuffle != 'vanilla'"
v-model="set.linked_drops" name="linked_drops" generator="base" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Dungeon Shuffle:</b>
</template>
<SettingPicker color="danger" v-model="set.door_shuffle" name="door_shuffle" generator="base" />
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
v-model="set.door_lobbies" name="door_lobbies" generator="base" />
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
v-model="set.door_type_mode" name="door_type_mode" generator="base" />
<SettingPicker color="danger" v-if="this.set.door_shuffle != 'vanilla'"
v-model="set.trap_door_mode" name="trap_door_mode" generator="base" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Enemizer:</b>
</template>
<SettingPicker color="success" v-model="set.boss_shuffle" name="boss_shuffle" generator="base" />
<SettingPicker color="success" v-model="set.enemy_shuffle" name="enemy_shuffle" generator="base" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Dungeon Item Shuffle:</b>
</template>
<SettingPicker color="warning" v-model="set.small_keys" name="small_keys" generator="base" />
<SettingPicker color="warning" v-model="set.big_keys" name="big_keys" generator="base" />
<SettingPicker color="warning" v-model="set.maps" name="maps" generator="base" />
<SettingPicker color="warning" v-model="set.compasses" name="compasses" generator="base" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Other Pool Settings:</b>
</template>
<SettingPicker color="danger" v-model="set.shop_shuffle" name="shop_shuffle" generator="base" />
<SettingPicker color="danger" v-model="set.drop_shuffle" name="drop_shuffle" generator="base" />
<SettingPicker color="danger" v-model="set.pottery" name="pottery" generator="base" />
<SettingPicker color="danger" v-model="set.prize_shuffle" name="prize_shuffle" generator="base" />
</AccordionItem>
<AccordionItem :expanded="false">
<template #header>
<b>Item Settings:</b>
</template>
<SettingPicker color="primary" v-model="set.boots" name="boots" generator="base" />
<SettingPicker color="primary" v-model="set.flute" name="flute" generator="base" />
<SettingPicker color="primary" v-model="set.dark_rooms" name="dark_rooms" generator="base" />
<SettingPicker color="primary" v-model="set.bombs" name="bombs" generator="base" />
<SettingPicker color="primary" v-model="set.book" name="book" generator="base" />
</AccordionItem>
<SettingsPage v-model="set" prefix="" />
</div>
<div class="card-footer">
<div class="nav nav-pills nav-fill" role="group">

15
src/views/MultiView.vue Normal file
View File

@@ -0,0 +1,15 @@
<script>
import { defineComponent } from 'vue';
import Multi from "@/components/Multi.vue";
export default defineComponent({
components: {
Multi,
},
});
</script>
<template>
<Multi :id="$route.params.id" />
</template>

View File

@@ -1,7 +1,7 @@
<script>
import { defineComponent } from 'vue';
import Seed from '../components/Seed.vue';
import Seed from "@/components/Seed.vue";
export default defineComponent({
components: {