Как динамически изменять содержимое компонента с помощью JSON?

Я создаю свое дизайнерское портфолио, используя Vue CLI 3. Архитектура моего веб-сайта очень проста. У меня есть домашняя страница, страница о странице, рабочая страница и несколько отдельных страниц проекта:

  • Home
    • About
    • Work
      • Project
      • Проект
      • Проект

Рабочая страница состоит из нескольких ссылок, по которым можно перейти на отдельные страницы проекта. Рабочий компонент настроен следующим образом:

<template>
  <div>
    <projectLink v-for="data in projectLinkJson" />
  </div>
</template>

<script>
import projectLink from '@/components/projectLink.vue'
import json from '@/json/projectLink.json'

export default {
  name: 'work',
  data(){
    return{
      projectLinkJson: json
    }
  },
  components: {
    projectLink
  }
}
</script>

Как видите, я импортирую JSON для динамического отображения содержимого. Далее компонент projectLink можно увидеть в блоке кода ниже. В этом компоненте я передаю параметр в <router-link> с именем projectName

<template>
  <router-link :to="{ name: 'projectDetails', params: { name: projectName }}">
      <h1>{{ title }}</h1>
  </router-link>
</template>

<script>
export default {
  name: 'projectLink',
  props: {
    title: String,
    projectName: String
  }
}
</script>

Мой файл route.js настроен так:

const routes = [
   { path: '/', component: home },
   { path: '/about', component: about },
   { path: '/work', component: work },
   {
     path: "/work/:name",
     name: "projectDetails",
     props: true,
     component: projectDetails
   },
];

и мой JSON такой:

  {
    "0": {
      "title": "test",
      "projectName": "test"
    }
  }

Наконец, мой компонент projectDetails — это компонент, в котором у меня возникла эта проблема:

<template>
    <div>
      <div
        v-for="(data,index) in projectDetailsJson" v-if="index <= 1">
            <h1>{{ data.title }}</h1>
            <p>{{ data.description }}</p>
      </div>
    </div>
</template>

<script>
import json from '@/json/projectDetails.json'

export default {
  name: 'projectDetails',
  data(){
    return{
      projectDetailsJson: json
    }
  },
  props: {
    description: String,
    title: String
  }
}
</script>

Я успешно перенаправляюсь на нужный URL-адрес /project/'name'. Я хочу использовать компонент projectDetails в качестве основы для каждой из моих отдельных страниц проекта. Но как мне сделать это динамически? Я хочу получить данные из файла JSON и отобразить правильный объект из массива на основе имени, которое было передано в URL-адрес. Я не хочу повторять и отображать весь массив на странице. Я просто хочу, чтобы один проект отображался.


person Austin Branham    schedule 12.12.2019    source источник


Ответы (4)


Быстрое решение:

projectDetails.vue

<template>
    <div>
        <div>
            <h1>{{ projectDetails.title }}</h1>
            <p>{{ projectDetails.description }}</p>
        </div>
    </div>
</template>

<script>
    import json from '@/json/projectDetails.json';

    export default {
        name: 'projectDetails',
        props: {
            name: String,
        },
        data() {
            return {
                projectDetails: Object.values(json).find(project => project.title === this.name),
            };
        },
    };
</script>


На мой взгляд, лучшее решение:

Я не понимаю, что вы храните данные проекта в двух отдельных файлах JSON. Во время компиляции оба файла сохраняются в результирующий файл JavaScript. Не лучше ли хранить эти данные в 1 файле? Вам не обязательно использовать все свои данные в одном месте. Во-вторых, если у вас есть листинг проекта, то вы можете выполнить маршрутизацию с необязательным сегментом, и в зависимости от того, имеет ли сегмент значение или нет, отобразить листинг или данные конкретного проекта. Затем вы загружаете данные проекта только в одно место, и когда выбран один проект, передаете его данные компоненту рендеринга данных этого проекта. Вам больше нигде не нужно загружать этот файл JSON.

routes.js

import home from '@/components/home.vue';
import about from '@/components/about.vue';
import work from '@/components/work.vue';

const routes = [
    {path: '/', name: 'home', component: home},
    {path: '/about', name: 'about', component: about},
    {path: '/work/:name?', name: 'work', component: work, props: true},
];

export default routes;

work.vue

<template>
    <div>
        <project-details v-if="currentProject" :project="currentProject"/>
        <projectLink v-else 
                     v-for="project in projects"
                     v-bind="project"
                     v-bind:key="project.projectName"
        />
    </div>
</template>

<script>
    import projectLink from './projectLink';
    import projectDetails from './projectDetails';
    import json from '@/json/projectLink.json';

    export default {
        name: 'work',
        props: {
            name: String,
        },
        data() {
            return {
                projects: Object.values(json),
            };
        },
        computed: {
            currentProject() {
                if (this.name) {
                    return this.projects.find(
                        project => project.projectName === this.name,
                    );
                }
            },
        },
        components: {
            projectLink,
            projectDetails,
        },
    };
</script>

projectDetails.vue

<template>
    <div>
        <div>
            <h1>{{ project.title }}</h1>
            <p>{{ project.description }}</p>
        </div>
    </div>
</template>

<script>

    export default {
        name: 'projectDetails',
        props: {
            project: Object,
        },
    };
</script>

projectLink.vue (изменена только одна строка)

<router-link v-if="projectName" :to="{ name: 'work', params: { name: projectName }}">

Полный рабочий пример:

Vue.component("navigation", {
  template: "#navigation"
});

const Projects = {
  template: "#projects",
  props: ["projects"]
};
const Project = {
  template: "#project",
  props: ["project"]
};
const HomePage = {
  template: "#home"
};
const AboutPage = {
  template: "#about"
};
const WorkPage = {
  data() {
    return {
      projects: [{
          slug: "foo",
          name: "Foo",
          desc: "Fus Ro Dah"
        },
        {
          slug: "bar",
          name: "Bar",
          desc: "Lorem Ipsum"
        }
      ]
    };
  },
  props: {
    slug: String
  },
  template: "#work",
  components: {
    Projects,
    Project
  },
  computed: {
    currentProject() {
      if (this.slug) {
        return this.projects.find(project => project.slug === this.slug);
      }
    }
  }
};

const router = new VueRouter({
  routes: [{
      path: "/",
      name: "home",
      component: HomePage
    },
    {
      path: "/about",
      name: "about",
      component: AboutPage
    },
    {
      path: "/work/:slug?",
      name: "work",
      component: WorkPage,
      props: true
    }
  ]
});

new Vue({
  router,
  template: "#base"
}).$mount("#app");
ul.nav {
  list-style-type: none;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background-color: #333;
}

ul.nav>li {
  float: left;
}

ul.nav>li>a {
  display: block;
  color: white;
  text-align: center;
  padding: 14px 16px;
  text-decoration: none;
}

ul.nav>li>a:hover {
  background-color: #111;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.3/vue-router.min.js"></script>

<div id="app"></div>

<script type="text/x-template" id="base">
  <div id="app">
    <div>
      <navigation></navigation>
      <router-view></router-view>
    </div>
  </div>
</script>

<script type="text/x-template" id="navigation">
  <ul class="nav" id="navigation">
    <li>
      <router-link :to="{name: 'home'}">Home</router-link>
    </li>
    <li>
      <router-link :to="{name: 'about'}">About</router-link>
    </li>
    <li>
      <router-link :to="{name: 'work'}">Work</router-link>
    </li>
  </ul>
</script>

<script type="text/x-template" id="home">
  <div id="home">This is Home Page</div>
</script>

<script type="text/x-template" id="about">
  <div id="about">This is About Page</div>
</script>

<script type="text/x-template" id="work">
  <div id="work">
    <project v-if="currentProject" :project="currentProject"></project>
    <projects v-else :projects="projects"></projects>
  </div>
</script>

<script type="text/x-template" id="projects">
  <div id="projects">
    <ul>
      <li v-for="project in projects" :key="project.slug">
        <router-link :to="{name: 'work', params:{ slug: project.slug}}">{{project.name}}</router-link>
      </li>
    </ul>
  </div>
</script>

<script type="text/x-template" id="project">
  <div id="project">
    <h2>{{project.name}}</h2>
    <p>{{project.desc}}</p>
  </div>
</script>

person Gander    schedule 17.12.2019
comment
Гандер, спасибо за хорошо продуманный ответ. Вы прекрасно понимаете, что хотите объединить мой JSON в один файл. Оба решения подходят для моего случая, но я собираюсь сохранить все данные в одном файле. Благодаря тонну!! - person Austin Branham; 24.12.2019
comment
Гандер, чтобы сделать еще один шаг вперед. Как бы вы отобразили только те ссылки проекта, которые соответствуют текущему URL-адресу? Итак, если у меня есть три разных типа проекта JSON: дизайн, код, движение. Если URL-адрес содержит движение, как мне отфильтровать мои компоненты projectLink, чтобы показать только те, у которых есть совпадающее значение JSON для дизайна, кода или движения. По сути, я просто пытаюсь фильтровать. - person Austin Branham; 07.12.2020
comment
@AustinBranham Я думаю, вам следует задать новый вопрос (новую тему), представив в качестве примера ваш текущий код. А также код, который вы пытались достичь своей новой цели. Прошел год, мои знания изменились, хотелось бы решить ее как новую проблему, а не обновлять старую, тем более, что она уже решена и принята. - person Gander; 12.12.2020

Отличная работа, Остин! Вы очень близки к тому, чтобы это сработало. Есть несколько разных способов разобрать правильные данные из файла JSON в компонент projectDetails, но я просто продемонстрирую свой предпочтительный способ.

Во-первых, вам понадобится немного ванильного JS для поиска в файле JSON и возврата только той строки, которую вы хотите. Я бы сделал это как метод, поскольку данные не будут меняться или требовать повторного рендеринга компонента. Итак, после вашего реквизита я бы добавил что-то вроде этого:

methods: {
  findProject(projectName) {
    return Object.values(json).find(project => project.title === projectName)
  }
}

Обратите внимание, что будет возвращен первый проект, имя которого соответствует названию проекта. Если у вас есть проекты с одинаковым именем, это не сработает.

Затем вам просто нужно обновить значение по умолчанию projectDetailsJson, чтобы вызвать этот метод и передать имя проекта маршрута. Обновите данные примерно так:

data() {
  return {
    projectDetailsJson: this.findProject(this.$route.params.name)
  }
}

Если это не сработает, нам может понадобиться установить projectDetailsJson в хуке жизненного цикла created, но сначала попробуйте приведенный выше код.

person Colin    schedule 12.12.2019
comment
Колин, огромное спасибо за помощь! Я пробовал ваш код выше, но он не работал для меня. Я понимаю, что ты там делаешь, просто не настолько, чтобы попытаться изменить что-то вокруг себя, чтобы заставить это работать. Как именно должен быть структурирован мой файл JSON, чтобы этот метод работал правильно? Кроме того, я получаю только одно предупреждение в консоли. Это может быть возможным быть вкладом? Когда я нахожусь на рабочей странице и щелкаю, чтобы перейти на страницу проекта, консоль говорит: [vue-router] отсутствует параметр для именованного маршрута. ProjectDetails: Ожидаемое имя будет определено. - person Austin Branham; 13.12.2019
comment
Рад помочь. Я собрал простую демонстрацию поиска json, чтобы показать вам, как должен быть структурирован JSON: repl.it/@ ColinUlin/DelayedGregariousAutoresponder - person Colin; 13.12.2019
comment
Обратите внимание, что в примере я использую require вместо import. Это просто из-за ограничения repl.it, поэтому просто игнорируйте его. Он делает то же самое, что и ваш import json from ... - person Colin; 13.12.2019
comment
Эта ошибка также кажется актуальной. Можете ли вы поставить console.log(this.$route.params) над возвратом в вашем методе findProject и сказать мне, что отображается в вашей консоли? - person Colin; 13.12.2019
comment
Итак, я поставил оператор v-if, который исправил эту ошибку. Этот console.log возвращает правильное имя проекта. Данные также правильно структурированы с объектами внутри объектов. Когда я использую vue-devtools, я вижу, что компонент возвращает правильные данные на основе имени. Но кажется, что он не правильно заполняет данные в полях. Может проблема с моим реквизитом? Я просто не могу понять это. Кроме того, когда я запускаю console.log(projectName) в созданном хуке жизненного цикла (и я передаю функцию projectName, она возвращает undefined. - person Austin Branham; 14.12.2019
comment
Я заархивировал свою кодовую базу здесь, на моем диске g. Честно говоря, я был бы бесконечно благодарен, если бы вы могли взглянуть на это. Я чувствую, что просто делаю крошечную ошибку, но не могу понять, в чем дело. Это примерно 105 МБ из-за папки модулей узла. Заранее спасибо за любую помощь, Колин! drive.google.com/file/d/1YwOoH07SlAPX8R5tu09_0vLPYGQMrxtq/ - person Austin Branham; 14.12.2019

Если я правильно понял, вы хотите сохранить родительский компонент в качестве макета для всей своей страницы?

Если я всегда правильно понимал, вы должны использовать свойство children vuerouter

https://router.vuejs.org/guide/essentials/nested-routes.html

import layout from 'layout';

const projectRoute = {
    path: '/project',
    component: Layout, // Load your layout
    redirect: '/project/list',
    name: 'Project',
    children: [
        {
            path: "list", // here the path become /project/list
            component: () => import('@/views/project/List'), // load your components
            name: "List of project",
        },
        {
            path: "detail/:id",
            component: () => import('@/views/project/Detail'), 
            name: "Detail of project",
        }
    ],
};

Таким образом, вы можете создать свой макет и добавить все, что хотите, это будет доступно для всех дочерних компонентов, и вы можете использовать $emit, $refs $props ect...

+

Вы можете создать файл routes/index.js и создать папку routes/modules . Внутри этого вы можете добавить свой routes/modules/project.js и загрузить модули в routes/index.js

import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

import projectRoutes from "./modules/project";

const routes = [
    projectRoutes,
    {
        // other routes.... 
    },
]


export default new VueRouter({
    routes,
    mode: 'history',
    history: true,
});    

@см. тот же документ: https://router.vuejs.org/guide/essentials/nested-routes.html

Наконец, вам просто нужно выполнить обработку layout и использовать props для распределения значений как в detail, так и в project list; и используйте методы фильтрации, описанные чуть выше

Надеюсь, я понял вашу просьбу, если это не так, дайте мне знать,

Увидимся

Изменить: вот очень красивая архитектура с vue, vuex и vuerouter. может вдохновить вас https://github.com/tuandm/laravue/tree/master/resources/js

person Alexis Gatuingt    schedule 17.12.2019

Для всех, чтобы сделать еще один шаг вперед. Как бы вы отобразили только те ссылки проекта, которые соответствуют текущему URL-адресу? Итак, если у меня есть три разных типа проекта JSON: дизайн, код, движение. Если URL-адрес содержит движение, как мне отфильтровать мои компоненты projectLink, чтобы показать только те, которые имеют совпадающее значение JSON для дизайна, кода или движения. По сути, я просто пытаюсь фильтровать.

person Austin Branham    schedule 07.12.2020