init
This commit is contained in:
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
12255
package-lock.json
generated
Normal file
12255
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
package.json
Normal file
49
package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "vue-blog-habr",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"bootstrap-vue": "^2.1.0",
|
||||
"core-js": "^3.4.4",
|
||||
"sass-loader": "^8.0.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-router": "^3.1.3",
|
||||
"vuex": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.0",
|
||||
"@vue/cli-plugin-eslint": "^4.1.0",
|
||||
"@vue/cli-service": "^4.1.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"node-sass": "^4.13.0",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
17
public/index.html
Normal file
17
public/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>vue-blog-habr</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but vue-blog-habr doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
33
src/App.vue
Normal file
33
src/App.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<template v-if="is404">
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<Header></Header>
|
||||
<main>
|
||||
<b-container>
|
||||
<router-view></router-view>
|
||||
</b-container>
|
||||
</main>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import '@/styles/index.scss';
|
||||
import Header from '@/components/Header.vue';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Header,
|
||||
},
|
||||
computed: {
|
||||
is404() {
|
||||
return this.$route.name === '404';
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
62
src/api/index.js
Normal file
62
src/api/index.js
Normal file
@ -0,0 +1,62 @@
|
||||
import Article from '@/models/Article';
|
||||
import Comment from '@/models/Comment';
|
||||
import Category from '@/models/Category';
|
||||
|
||||
export default {
|
||||
getArticles() {
|
||||
const comments = this.getComments();
|
||||
const items = [
|
||||
{
|
||||
id: 1, title: 'Статья 1', content: 'Содержание статьи 1',
|
||||
},
|
||||
{
|
||||
id: 2, title: 'Статья 2', content: 'Содержание статьи 2',
|
||||
},
|
||||
{
|
||||
id: 3, title: 'Статья 3', content: 'Содержание статьи 3',
|
||||
},
|
||||
{
|
||||
id: 4, title: 'Статья 4', content: 'Содержание статьи 4',
|
||||
},
|
||||
{
|
||||
id: 5, title: 'Статья 5', content: 'Содержание статьи 5',
|
||||
},
|
||||
{
|
||||
id: 6, title: 'Статья 6', content: 'Содержание статьи 6',
|
||||
},
|
||||
];
|
||||
return items.map((item) => {
|
||||
const article = Article.createFrom(item);
|
||||
article.comments = comments.filter((comment) => comment.article_id == article.id);
|
||||
|
||||
return article;
|
||||
});
|
||||
},
|
||||
getComments() {
|
||||
const items = [
|
||||
{
|
||||
id: 1, article_id: 1, content: 'Комментарий к статье 1',
|
||||
},
|
||||
];
|
||||
return items.map((item) => Comment.createFrom(item))
|
||||
},
|
||||
getCategories() {
|
||||
const items = [
|
||||
{
|
||||
id: 1, title: 'Новости', articles: [1,3,5],
|
||||
},
|
||||
{
|
||||
id: 2, title: 'Спорт', articles: [2,3,4],
|
||||
},
|
||||
{
|
||||
id: 3, title: 'Красота', articles: [],
|
||||
},
|
||||
];
|
||||
return items.map((item) => Category.createFrom(item))
|
||||
},
|
||||
addComment(comment) {
|
||||
if (comment) {
|
||||
// отправка запроса на бэк
|
||||
}
|
||||
},
|
||||
};
|
||||
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
34
src/components/ArticleItem.vue
Normal file
34
src/components/ArticleItem.vue
Normal file
@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<b-card :title="item.title" class="article-item-card">
|
||||
<router-link :to="getArticleRoute" class="card-link">
|
||||
Подробнее
|
||||
</router-link>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Article from '@/models/Article';
|
||||
|
||||
export default {
|
||||
name: 'ArticleItem',
|
||||
props: {
|
||||
item: Article,
|
||||
},
|
||||
computed: {
|
||||
getArticleRoute() {
|
||||
return {
|
||||
name: 'Article',
|
||||
params: {
|
||||
post_id: this.item.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.article-item-card {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
39
src/components/ArticleItems.vue
Normal file
39
src/components/ArticleItems.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<ListItems :items="items" class="row">
|
||||
<template v-slot:default="props">
|
||||
<b-col :cols="itemCols">
|
||||
<ArticleItem :item="props.item"></ArticleItem>
|
||||
</b-col>
|
||||
</template>
|
||||
<template v-slot:empty>
|
||||
<b-col>
|
||||
Статьи еще пишутся :)
|
||||
</b-col>
|
||||
</template>
|
||||
</ListItems>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItems from '@/components/ListItems.vue'
|
||||
import ArticleItem from '@/components/ArticleItem.vue'
|
||||
|
||||
export default {
|
||||
name: 'ArticleItems',
|
||||
components: {
|
||||
ArticleItem,
|
||||
ListItems,
|
||||
},
|
||||
extends: ListItems,
|
||||
props: {
|
||||
cols: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
itemCols() {
|
||||
return 12 / this.cols;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
16
src/components/CategoryItem.vue
Normal file
16
src/components/CategoryItem.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Category from '@/models/Category';
|
||||
|
||||
export default {
|
||||
name: 'CategoryItem',
|
||||
props: {
|
||||
item: Category,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
32
src/components/CommentForm.vue
Normal file
32
src/components/CommentForm.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<textarea class='form-control' v-model="content"></textarea>
|
||||
<br>
|
||||
<button type="submit" class="btn btn-primary">Сохранить</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CommentForm',
|
||||
props: {
|
||||
articleId: Number,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
content: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
if (this.content) {
|
||||
this.$store.dispatch('addComment', {
|
||||
content: this.content,
|
||||
article_id: this.articleId,
|
||||
});
|
||||
this.content = '';
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
31
src/components/CommentItem.vue
Normal file
31
src/components/CommentItem.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<b-card>
|
||||
<b-card-text>
|
||||
{{ item.content }}
|
||||
</b-card-text>
|
||||
<router-link :to="getArticleRoute">
|
||||
Перейти к статье
|
||||
</router-link>
|
||||
</b-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Comment from '@/models/Comment';
|
||||
|
||||
export default {
|
||||
name: 'CommentItem',
|
||||
props: {
|
||||
item: Comment,
|
||||
},
|
||||
computed: {
|
||||
getArticleRoute() {
|
||||
return {
|
||||
name: 'Article',
|
||||
params: {
|
||||
post_id: this.item.article_id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
16
src/components/Header.vue
Normal file
16
src/components/Header.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<b-container>
|
||||
<b-navbar>
|
||||
<b-navbar-brand to="/">
|
||||
<img alt="Vue logo" src="@/assets/logo.png" style="height:50px;">
|
||||
Vue-blog-habr
|
||||
</b-navbar-brand>
|
||||
</b-navbar>
|
||||
</b-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Header',
|
||||
}
|
||||
</script>
|
||||
29
src/components/ListItems.vue
Normal file
29
src/components/ListItems.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class='list-items'>
|
||||
<template v-if='items && items.length'>
|
||||
|
||||
<template v-for='(item, index) in items'>
|
||||
<slot :item="item" :index="index">
|
||||
Элемент {{ index }}
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
<slot name="empty">
|
||||
Нет элементов
|
||||
</slot>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ListItems',
|
||||
props: {
|
||||
items: Array,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
18
src/main.js
Normal file
18
src/main.js
Normal file
@ -0,0 +1,18 @@
|
||||
import App from './App.vue'
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import BootstrapVue from 'bootstrap-vue'
|
||||
|
||||
import store from './store'
|
||||
import router from './router'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
new Vue({
|
||||
store,
|
||||
router,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
18
src/models/Article.js
Normal file
18
src/models/Article.js
Normal file
@ -0,0 +1,18 @@
|
||||
export default class Article
|
||||
{
|
||||
constructor(id, title, content) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.comments = [];
|
||||
}
|
||||
|
||||
addComment(item) {
|
||||
this.comments.push(item);
|
||||
}
|
||||
|
||||
static createFrom(data) {
|
||||
const {id, title, content} = data;
|
||||
return new this(id, title, content);
|
||||
}
|
||||
}
|
||||
13
src/models/Category.js
Normal file
13
src/models/Category.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default class Category
|
||||
{
|
||||
constructor(id, title, articles) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.articles = articles;
|
||||
}
|
||||
|
||||
static createFrom(data) {
|
||||
const {id, title, articles} = data;
|
||||
return new this(id, title, articles);
|
||||
}
|
||||
}
|
||||
13
src/models/Comment.js
Normal file
13
src/models/Comment.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default class Comment
|
||||
{
|
||||
constructor(id, content, article_id) {
|
||||
this.id = id;
|
||||
this.content = content;
|
||||
this.article_id = article_id;
|
||||
}
|
||||
|
||||
static createFrom(data) {
|
||||
const {id, content, article_id} = data;
|
||||
return new this(id, content, article_id);
|
||||
}
|
||||
}
|
||||
11
src/pages/404.vue
Normal file
11
src/pages/404.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
Ничего не найдено
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Article',
|
||||
}
|
||||
</script>
|
||||
88
src/pages/Article.vue
Normal file
88
src/pages/Article.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<b-row v-if="article">
|
||||
<b-col md="8" lg="9">
|
||||
<h1>
|
||||
{{ article.title }}
|
||||
</h1>
|
||||
<p class="mb-4">
|
||||
{{ article.content }}
|
||||
</p>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="w-50">
|
||||
<router-link v-if="prevArticle" :to="getArticleRoute(prevArticle)">
|
||||
{{ prevArticle.title }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<router-link v-if="nextArticle" :to="getArticleRoute(nextArticle)">
|
||||
{{ nextArticle.title }}
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<CommentForm :articleId="article.id"></CommentForm>
|
||||
<CommentItem v-for="(item, index) in article.comments" :key="index" :item="item"></CommentItem>
|
||||
</b-col>
|
||||
<b-col md="4" lg="3">
|
||||
<CommentItem v-for="(item, index) in lastComments" :key="index" :item="item"></CommentItem>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommentForm from '@/components/CommentForm.vue';
|
||||
import CommentItem from '@/components/CommentItem.vue';
|
||||
import {
|
||||
mapActions,
|
||||
mapGetters,
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Article',
|
||||
components: {
|
||||
CommentForm,
|
||||
CommentItem,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'lastComments',
|
||||
'nextArticle',
|
||||
'prevArticle',
|
||||
]),
|
||||
articleId() {
|
||||
return this.$route.params['post_id'] || null;
|
||||
},
|
||||
article() {
|
||||
return this.$store.state.blog.activeArticle;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'loadComments',
|
||||
'loadActiveArticle',
|
||||
]),
|
||||
getArticleRoute(item) {
|
||||
return {
|
||||
name: 'Article',
|
||||
params: {
|
||||
post_id: item.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadComments();
|
||||
this.loadActiveArticle(this.articleId);
|
||||
},
|
||||
watch: {
|
||||
articleId(value) {
|
||||
this.loadActiveArticle(value);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
46
src/pages/Category.vue
Normal file
46
src/pages/Category.vue
Normal file
@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="category">
|
||||
<h1>
|
||||
{{ category.title }}
|
||||
</h1>
|
||||
<ArticleItems :items="articles" :cols="2"></ArticleItems>
|
||||
</div>
|
||||
<div v-else>
|
||||
Категория не найдена
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArticleItems from '@/components/ArticleItems.vue'
|
||||
import {
|
||||
mapActions,
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Category',
|
||||
components: {
|
||||
ArticleItems,
|
||||
},
|
||||
computed: {
|
||||
categoryId() {
|
||||
return this.$route.params['category_id'] || null;
|
||||
},
|
||||
category() {
|
||||
return this.$store.state.blog.activeCategory;
|
||||
},
|
||||
articles() {
|
||||
return this.$store.getters.activeCategoryArticles;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'loadActiveCategory',
|
||||
]),
|
||||
},
|
||||
mounted() {
|
||||
this.loadActiveCategory(this.categoryId);
|
||||
},
|
||||
}
|
||||
</script>
|
||||
61
src/pages/Index.vue
Normal file
61
src/pages/Index.vue
Normal file
@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<b-row>
|
||||
<b-col md="8" lg="9">
|
||||
<ArticleItems :items="lastArticles"></ArticleItems>
|
||||
</b-col>
|
||||
<b-col md="4" lg="3">
|
||||
<ListItems :items="popularCategories" v-slot="props">
|
||||
<router-link :to="getCategoryRoute(props.item)">
|
||||
{{ props.item.title }}
|
||||
</router-link>
|
||||
</ListItems>
|
||||
<CommentItem v-for="(item, index) in lastComments" :key="index" :item="item"></CommentItem>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ListItems from '@/components/ListItems.vue'
|
||||
import ArticleItems from '@/components/ArticleItems.vue'
|
||||
import CommentItem from '@/components/CommentItem.vue'
|
||||
import {
|
||||
mapGetters,
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
components: {
|
||||
ListItems,
|
||||
CommentItem,
|
||||
ArticleItems,
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
getCategoryRoute(item) {
|
||||
return {
|
||||
name: 'Category',
|
||||
params: {
|
||||
category_id: item.id,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'lastArticles',
|
||||
'lastComments',
|
||||
'popularCategories',
|
||||
]),
|
||||
},
|
||||
created() {
|
||||
/**
|
||||
* Запросы делаем к действиям (а не мутациям)
|
||||
*/
|
||||
this.$store.dispatch('loadArticles');
|
||||
this.$store.dispatch('loadComments');
|
||||
this.$store.dispatch('loadCategories');
|
||||
},
|
||||
}
|
||||
</script>
|
||||
12
src/router/blog/index.js
Normal file
12
src/router/blog/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
export default [
|
||||
{
|
||||
path: '/cat-:category_id',
|
||||
name: 'Category',
|
||||
component: () => import('@/pages/Category.vue'),
|
||||
},
|
||||
{
|
||||
path: '/post-:post_id',
|
||||
name: 'Article',
|
||||
component: () => import('@/pages/Article.vue'),
|
||||
},
|
||||
];
|
||||
24
src/router/index.js
Normal file
24
src/router/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
import VueRouter from 'vue-router'
|
||||
import blog from './blog'
|
||||
|
||||
export default new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Index',
|
||||
component: () => import('@/pages/Index.vue'),
|
||||
},
|
||||
/**
|
||||
* Когда проект большой, роуты лучше выносить в отдельные файлы
|
||||
* Распределенные по модулям приложения
|
||||
* В данном случае это излишне и это просто демонстрация
|
||||
*/
|
||||
...blog,
|
||||
{
|
||||
path: '*',
|
||||
name: '404',
|
||||
component: () => import('@/pages/404.vue'),
|
||||
},
|
||||
]
|
||||
})
|
||||
25
src/services/Display.js
Normal file
25
src/services/Display.js
Normal file
@ -0,0 +1,25 @@
|
||||
export default {
|
||||
/**
|
||||
* Получить координаты элемента в документе
|
||||
* @param {DOMElement} elem
|
||||
*/
|
||||
getCoords(elem) {
|
||||
const box = elem.getBoundingClientRect();
|
||||
const body = document.body;
|
||||
const docEl = document.documentElement;
|
||||
|
||||
const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
|
||||
const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
|
||||
|
||||
const clientTop = docEl.clientTop || body.clientTop || 0;
|
||||
const clientLeft = docEl.clientLeft || body.clientLeft || 0;
|
||||
|
||||
const top = box.top + scrollTop - clientTop;
|
||||
const left = box.left + scrollLeft - clientLeft;
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
};
|
||||
}
|
||||
};
|
||||
11
src/store/index.js
Normal file
11
src/store/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import blog from './modules/blog'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
blog,
|
||||
},
|
||||
})
|
||||
132
src/store/modules/blog.js
Normal file
132
src/store/modules/blog.js
Normal file
@ -0,0 +1,132 @@
|
||||
import Api from '@/api';
|
||||
import Comment from '@/models/Comment';
|
||||
|
||||
export default {
|
||||
state: {
|
||||
articles: [],
|
||||
comments: [],
|
||||
categories: [],
|
||||
//
|
||||
activeArticle: null,
|
||||
activeCategory: null,
|
||||
},
|
||||
getters: {
|
||||
lastArticles(state) {
|
||||
return state.articles.slice(0, 10);
|
||||
},
|
||||
lastComments(state) {
|
||||
return state.comments.slice(0, 10);
|
||||
},
|
||||
popularCategories(state) {
|
||||
return state.categories.slice(0, 10);
|
||||
},
|
||||
activeCategoryArticles(state) {
|
||||
if (!state.activeCategory) {
|
||||
return [];
|
||||
}
|
||||
return state.articles.filter((item) => state.activeCategory.articles.indexOf(item.id) >= 0);
|
||||
},
|
||||
activeArticleComments(state) {
|
||||
if (!state.activeArticle) {
|
||||
return [];
|
||||
}
|
||||
return state.comments.filter((item) => state.activeArticle.id == item.article_id);
|
||||
},
|
||||
prevArticle(state) {
|
||||
let prevItem = null;
|
||||
if (state.activeArticle) {
|
||||
state.articles.forEach((item, index) => {
|
||||
if (item.id == state.activeArticle.id) {
|
||||
prevItem = state.articles[index-1] || null;
|
||||
}
|
||||
});
|
||||
}
|
||||
return prevItem;
|
||||
},
|
||||
nextArticle(state) {
|
||||
let nextItem = null;
|
||||
if (state.activeArticle) {
|
||||
state.articles.forEach((item, index) => {
|
||||
if (item.id == state.activeArticle.id) {
|
||||
nextItem = state.articles[index+1] || null;
|
||||
}
|
||||
});
|
||||
}
|
||||
return nextItem;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setArticles(state, payload) {
|
||||
state.articles = payload.items;
|
||||
},
|
||||
setComments(state, payload) {
|
||||
state.comments = payload.items;
|
||||
},
|
||||
setCategories(state, payload) {
|
||||
state.categories = payload.items;
|
||||
},
|
||||
setActiveCategory(state, payload) {
|
||||
state.activeCategory = payload;
|
||||
},
|
||||
setActiveArticle(state, payload) {
|
||||
state.activeArticle = payload;
|
||||
},
|
||||
addComment(state, payload) {
|
||||
state.comments.push(payload);
|
||||
state.activeArticle.addComment(Comment.createFrom(payload));
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async loadArticles({ commit, state }) {
|
||||
if (state.articles.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = await Api.getArticles();
|
||||
commit('setArticles', {
|
||||
items
|
||||
});
|
||||
},
|
||||
async loadComments({ commit, state }) {
|
||||
if (state.comments.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = await Api.getComments();
|
||||
commit('setComments', {
|
||||
items
|
||||
});
|
||||
},
|
||||
async loadCategories({ commit, state }) {
|
||||
if (state.categories.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = await Api.getCategories();
|
||||
commit('setCategories', {
|
||||
items
|
||||
});
|
||||
},
|
||||
async loadActiveCategory(context, id) {
|
||||
await context.dispatch('loadArticles');
|
||||
await context.dispatch('loadCategories');
|
||||
|
||||
let category = context.state.categories.find((item) => {
|
||||
return item.id == id;
|
||||
});
|
||||
context.commit('setActiveCategory', category);
|
||||
},
|
||||
async loadActiveArticle(context, id) {
|
||||
await context.dispatch('loadArticles');
|
||||
|
||||
let model = context.state.articles.find((item) => {
|
||||
return item.id == id;
|
||||
});
|
||||
context.commit('setActiveArticle', model);
|
||||
},
|
||||
async addComment({ commit }, payload) {
|
||||
await Api.addComment(payload);
|
||||
commit('addComment', payload);
|
||||
},
|
||||
},
|
||||
}
|
||||
2
src/styles/index.scss
Normal file
2
src/styles/index.scss
Normal file
@ -0,0 +1,2 @@
|
||||
@import 'node_modules/bootstrap/scss/bootstrap';
|
||||
@import 'node_modules/bootstrap-vue/src/index.scss';
|
||||
Reference in New Issue
Block a user