v.0.1.0 pinia, albums-store
This commit is contained in:
Generated
+44
-7
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "tmp-app-creation",
|
"name": "tmp-app-creation",
|
||||||
"version": "0.0.0",
|
"version": "0.0.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "tmp-app-creation",
|
"name": "tmp-app-creation",
|
||||||
"version": "0.0.0",
|
"version": "0.0.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@types/axios": "^0.9.36",
|
"@types/axios": "^0.9.36",
|
||||||
"axios": "^1.20.0",
|
"axios": "^1.20.0",
|
||||||
|
"pinia": "^4.0.3",
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
"vue": "^3.5.42"
|
"vue": "^3.5.42"
|
||||||
},
|
},
|
||||||
@@ -1469,6 +1470,16 @@
|
|||||||
"@vue/shared": "3.5.42"
|
"@vue/shared": "3.5.42"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vue/devtools-api": {
|
||||||
|
"version": "8.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz",
|
||||||
|
"integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-kit": "^8.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vue/devtools-core": {
|
"node_modules/@vue/devtools-core": {
|
||||||
"version": "8.2.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz",
|
||||||
@@ -1487,7 +1498,6 @@
|
|||||||
"version": "8.2.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz",
|
||||||
"integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==",
|
"integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/devtools-shared": "^8.2.1",
|
"@vue/devtools-shared": "^8.2.1",
|
||||||
@@ -1500,7 +1510,6 @@
|
|||||||
"version": "8.2.1",
|
"version": "8.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz",
|
||||||
"integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==",
|
"integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@vue/language-core": {
|
"node_modules/@vue/language-core": {
|
||||||
@@ -1663,7 +1672,6 @@
|
|||||||
"version": "2.9.0",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
|
||||||
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
@@ -2195,7 +2203,6 @@
|
|||||||
"version": "5.5.3",
|
"version": "5.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
|
||||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
@@ -2702,6 +2709,12 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nostics": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/npm-normalize-package-bin": {
|
"node_modules/npm-normalize-package-bin": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz",
|
||||||
@@ -2809,7 +2822,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
|
||||||
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
|
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
@@ -2843,6 +2855,31 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-XMQqpvjgG7LMqVhhFUzKLT4KEbsYbfZZ0CZU9PgdFm1O2VKBmIkykL8+SfNhOaT7sR0wT6BEgKT0YNDYWgmVRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nostics": "^1.1.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/devtools-api": "^8.1.5",
|
||||||
|
"typescript": ">=5.6.0",
|
||||||
|
"vue": "^3.5.11"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/devtools-api": {
|
||||||
|
"optional": false
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.28",
|
"version": "8.5.28",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@tailwindcss/vite": "^4.3.3",
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
"@types/axios": "^0.9.36",
|
"@types/axios": "^0.9.36",
|
||||||
"axios": "^1.20.0",
|
"axios": "^1.20.0",
|
||||||
|
"pinia": "^4.0.3",
|
||||||
"tailwindcss": "^4.3.3",
|
"tailwindcss": "^4.3.3",
|
||||||
"vue": "^3.5.42"
|
"vue": "^3.5.42"
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-2
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import FirstVueComponent from "./components/FirstVueComponent.vue";
|
import FirstVueComponent from "./components/FirstVueComponent.vue";
|
||||||
import SecondVueComponent from "./components/SecondVueComponent.vue";
|
import SecondVueComponent from "./components/SecondVueComponent.vue";
|
||||||
import FetchFromApi from "./components/FetchFromApi.vue";
|
|
||||||
|
|
||||||
import pkg from "../package.json"; // Works out-of-the-box in Vite
|
import pkg from "../package.json"; // Works out-of-the-box in Vite
|
||||||
|
|
||||||
import pool_bg from "@/assets/images/swimming-pool.jpg";
|
import pool_bg from "@/assets/images/swimming-pool.jpg";
|
||||||
import type { Album } from "./types/album.ts";
|
import type { Album } from "./types/album.ts";
|
||||||
|
import AlbumsList from "./components/AlbumsList.vue";
|
||||||
|
import AlbumTitles from "./components/AlbumTitles.vue";
|
||||||
|
|
||||||
document.title = `${pkg.name} ${pkg.version}©2026`;
|
document.title = `${pkg.name} ${pkg.version}©2026`;
|
||||||
|
|
||||||
@@ -25,7 +26,8 @@ const sample_album: Album = {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<FirstVueComponent class="component-box-fade" />
|
<FirstVueComponent class="component-box-fade" />
|
||||||
<SecondVueComponent :album="sample_album" />
|
<SecondVueComponent :album="sample_album" />
|
||||||
<FetchFromApi class="component-box-fade" />
|
<AlbumsList />
|
||||||
|
<AlbumTitles />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<!-- src/components/AlbumTitles.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useAlbumsStore } from "@/stores/albums";
|
||||||
|
|
||||||
|
const { titles, count } = storeToRefs(useAlbumsStore());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p>{{ count }} albums loaded</p>
|
||||||
|
<ul>
|
||||||
|
<li v-for="t in titles" :key="t">{{ t }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<!-- src/components/AlbumsList.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useAlbumsStore } from "@/stores/albums";
|
||||||
|
import AlbumCard from "@/components/AlbumCard.vue";
|
||||||
|
|
||||||
|
const store = useAlbumsStore();
|
||||||
|
|
||||||
|
// ⚠️ storeToRefs keeps reactivity for state + getters
|
||||||
|
const { albums, loading, error } = storeToRefs(store);
|
||||||
|
// actions can be destructured directly — they're bound
|
||||||
|
const { fetchAlbums } = store;
|
||||||
|
|
||||||
|
onMounted(() => fetchAlbums());
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="m-2.5 w-1/3 rounded-2xl bg-linear-to-br from-emerald-200 to-emerald-500 border-2"
|
||||||
|
>
|
||||||
|
<div v-if="loading" class="p-3">Loading…</div>
|
||||||
|
<div v-else-if="error" class="p-3 text-red-700">Failed to load albums.</div>
|
||||||
|
<div v-else class="p-1.5 flex flex-wrap gap-3">
|
||||||
|
<AlbumCard v-for="a in albums" :key="a.id" :album="a" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from "vue";
|
|
||||||
import axios from "axios";
|
|
||||||
import type { Album } from "@/types/album";
|
|
||||||
|
|
||||||
const album_titles = ref<string[]>([]);
|
|
||||||
|
|
||||||
onMounted(async (): Promise<void> => {
|
|
||||||
try {
|
|
||||||
// Axios automatically parses the JSON response into 'response.data'
|
|
||||||
const albums_response = await axios.get<Album[]>(
|
|
||||||
"https://jsonplaceholder.typicode.com/albums",
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("albums_response:", albums_response.data);
|
|
||||||
|
|
||||||
album_titles.value = albums_response.data
|
|
||||||
.slice(0, 12)
|
|
||||||
.map((album) => album.title);
|
|
||||||
|
|
||||||
console.log("album_titles:", [...album_titles.value]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching albums!", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="card">
|
|
||||||
<h3>Albums List</h3>
|
|
||||||
<ul>
|
|
||||||
<li v-for="(album_title, index) in album_titles" :key="index">
|
|
||||||
{{ album_title }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* Optional styling for your card */
|
|
||||||
ul {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -4,13 +4,20 @@ import { ref } from "vue";
|
|||||||
const msg = ref<string>("Tom is building a vue template, oh yeah");
|
const msg = ref<string>("Tom is building a vue template, oh yeah");
|
||||||
const status = ref<boolean>(true);
|
const status = ref<boolean>(true);
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
hide: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Define function directly
|
// Define function directly
|
||||||
const toggleStatus = (): void => {
|
const toggleStatus = (): void => {
|
||||||
status.value = !status.value;
|
status.value = !status.value;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template v-if="hide">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="tj">{{ msg }} {{ status }}</div>
|
<div class="tj">{{ msg }} {{ status }}</div>
|
||||||
<p v-if="status">{{ msg }} {{ status }}</p>
|
<p v-if="status">{{ msg }} {{ status }}</p>
|
||||||
|
|||||||
+4
-1
@@ -1,5 +1,8 @@
|
|||||||
|
import { createPinia } from "pinia";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "@/App.vue";
|
import App from "@/App.vue";
|
||||||
import "@/assets/styles/global.css";
|
import "@/assets/styles/global.css";
|
||||||
|
|
||||||
createApp(App).mount("#app");
|
const app = createApp(App);
|
||||||
|
app.use(createPinia());
|
||||||
|
app.mount("#app");
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// src/stores/albums.ts
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import axios from "axios";
|
||||||
|
import type { Album } from "@/types/album";
|
||||||
|
|
||||||
|
const API_URL = "https://jsonplaceholder.typicode.com/albums";
|
||||||
|
|
||||||
|
export const useAlbumsStore = defineStore("albums", () => {
|
||||||
|
// ---- state ----
|
||||||
|
const albums = ref<Album[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref<unknown>(null);
|
||||||
|
let loaded = false; // module-internal guard (not reactive)
|
||||||
|
|
||||||
|
// ---- getters ----
|
||||||
|
const titles = computed(() => albums.value.map((a) => a.title));
|
||||||
|
const count = computed(() => albums.value.length);
|
||||||
|
const isLoading = computed(() => loading.value);
|
||||||
|
|
||||||
|
// ---- actions ----
|
||||||
|
async function fetchAlbums(force = false) {
|
||||||
|
if (loaded && !force) return;
|
||||||
|
if (loading.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
error.value = null;
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get<Album[]>(API_URL);
|
||||||
|
albums.value = data.slice(0, 12);
|
||||||
|
loaded = true;
|
||||||
|
console.log("albums loaded:", albums.value.length);
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e;
|
||||||
|
console.error("Error fetching albums!", e);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
albums.value = [];
|
||||||
|
error.value = null;
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
albums,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
titles,
|
||||||
|
count,
|
||||||
|
isLoading,
|
||||||
|
fetchAlbums,
|
||||||
|
reset,
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user