十一、指令、插件、SSR 和其它

现在你进入职业联赛了!您是一名高级 Vue 开发人员。让我们有一些乐趣,看看一些伟大的食谱是定制为您!以下是一些精心挑选的优化解决方案,它们可以提高 Vue 应用的质量,让您的生活更轻松。

在本章中,我们将介绍以下配方:

  • 自动加载vue-router路线
  • 自动加载vuex模块
  • 创建自定义指令
  • 创建 Vue 插件
  • 利用类星体在 Vue 中创建 SSR、SPA、PWA、Cordova 和电子应用
  • 创建更智能的 Vue 监视程序和计算属性
  • 以 PythonFlask为 API 创建Nuxt.jsSSR
  • Vue 应用的注意事项

技术要求

在本章中,我们将使用 Node.js、[T0]、[T1]、[T2]、[T3]、[T4]和 Python。

**Attention Windows users: you are required to install an npm package called windows-build-tools to be able to install the following required packages. To do so, open PowerShell as an Administrator and execute the following command: > npm install -g windows-build-tools

要安装Vue-CLI,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> npm install -g @vue/cli @vue/cli-service-global

要安装Cordova,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> npm install -g cordova

如果您在 macOS 上运行并且希望运行 iOS 模拟器,则需要在终端(macOS)中执行以下命令:

> npm install -g ios-sim ios-deploy

要安装Electron您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> npm install -g electron

要安装Quasar您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> npm install -g @quasar/cli

要安装Nuxt.js您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> npm install -g create-nuxt-app

自动加载 Vue 路由

为了创建可维护的代码,我们可以在项目中使用自动导入具有相同结构的文件的策略。就像vue-router中的路由一样,当应用变大时,我们会发现大量文件被手动导入和处理。在本食谱中,我们将学习一个技巧,使用 webpackrequire.context函数为我们自动注入文件。

此函数将读取文件内容,并将路由添加到默认情况下将导出到文件中的数组中。您可以通过添加更受控制的路由导入或甚至基于环境的路由规则来改进此配方。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

我们需要使用Vue-CLI创建一个新的 Vue 项目,或者使用在以前的配方中创建的项目:

  1. 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create router-import
  1. CLI 将询问一些有助于创建项目的问题。您可以使用箭头键进行导航,输入键继续,空格键选择一个选项。

  2. 启动新项目有两种方法。默认方法是一个基本的babeleslint项目,没有任何插件或配置,Manually模式,您可以选择更多的模式、插件、过梁和选项。我们将选择Manually

? Please pick a preset: (Use arrow keys)
  default (babel, eslint) ❯ Manually select features ‌
  1. 现在,我们被问及我们希望在项目中使用的功能。这些特性是一些 Vue 插件,如VuexRoutervue-router)、测试器、linter 等等。选择BabelRouterLinter / Formatter
? Check the features needed for your project: (Use arrow keys) ❯ Babel
  TypeScript
  Progressive Web App (PWA) Support ❯ Router
  Vuex
  CSS Pre-processors ❯ Linter / Formatter
  Unit Testing
  E2E Testing
  1. 通过选择 linter 和 formatter 继续此过程。在本例中,我们将选择ESLint + Airbnb配置:
? Pick a linter / formatter config: (Use arrow keys)
  ESLint with error prevention only ❯ ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier
  1. 在设置了 linting 规则之后,我们需要定义它们何时应用于代码。可应用于on save或固定于on commit
? Pick additional lint features: (Use arrow keys)
  Lint on save ❯ Lint and fix on commit
  1. 在定义了所有这些插件、linter 和处理器之后,我们需要选择存储设置和配置的位置。存储它们的最佳位置是在专用文件中,但也可以将它们存储在package.json
? Where do you prefer placing config for Babel, ESLint, etc.? (Use 
  arrow keys) ❯ In dedicated config files 
  In package.json 
  1. 现在,您可以选择是否要将此选择设置为未来项目的预设,以便无需重新选择所有内容:
? Save this as a preset for future projects? (y/N) n

Vue-CLI将创建项目,并自动为我们安装软件包。‌

如果要在安装完成后在vue-ui上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:

> vue ui

也可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)运行内置的npm命令,并执行以下命令之一:

  • npm run serve-在本地运行开发服务器
  • npm run build-构建并缩小部署应用
  • npm run lint-在代码上执行 lint

怎么做。。。

按照以下说明在项目中创建路由文件的自动导入,以处理特定文件夹中的路由文件:

  1. 创建路由文件并将其放入routes文件夹后,我们需要确保每个路由文件中都有一个默认的export对象。在index.js文件中的src/router文件夹中,删除文件中存在的routes的默认数组:
import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

export default new VueRouter({});
  1. 现在创建一个空的[T0]数组,该数组将由从文件夹中导入的数组填充,并开始导入。这样,requireRoutes将是一个对象,键是文件名,值是文件 ID:
import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes = [];
const requireRoutes = require.context(
  './routes',
  true,
  /^(?!.*test).*\.js$/is,
);

const router = new VueRouter({
  routes,
});

export default router;
  1. 要将这些文件推送到routes数组中,我们需要添加以下代码,并在router文件夹中创建一个名为routes的文件夹:
import Vue from 'vue';
import VueRouter from 'vue-router';

Vue.use(VueRouter);

const routes = [];
const requireRoutes = require.context(
  './routes',
  true,
  /^(?!.*test).*\.js$/is,
);

requireRoutes.keys().forEach((fileName) => {
  routes.push({
    ...requireRoutes(fileName).default,
  });
});

const router = new VueRouter({
  routes,
});

export default router;

现在,当您在routes文件夹中创建一个新的.js文件时,您的路由将自动加载到应用中。

它是如何工作的。。。

require.context是一个网页包内置函数,允许您传入一个目录进行搜索,一个指示是否也应检查子目录的标志,以及一个匹配文件的正则表达式。

当构建过程开始时,webpack 将搜索所有require.context函数并预执行它们,因此导入所需的文件将在那里进行最终构建。

我们将三个参数传递给函数:第一个参数是开始搜索的文件夹,第二个参数询问搜索是否将转到降序文件夹,最后一个参数是用于文件名匹配的正则表达式。

在此配方中,为了自动加载路由作为函数的第一个参数,我们为文件夹定义了./routes。作为函数的第二个参数,我们将false定义为不在子目录中搜索。最后,作为第三个参数,我们将[T2]定义为正则表达式,用于搜索[T3]文件,并忽略名称中包含[T4]的文件。

还有更多。。。

有了这个方法,通过使用路由模块的子目录和用于路由控制的环境,可以将应用提升到下一个级别。

通过这些增量,可以将函数提取到另一个文件中,但在router.js中,仍需要将其导入main.js文件中。或者,您可以获取import函数,并将routes的数组传递给router.js

另见

在[T1]的网页包文档中阅读更多关于网页包依赖关系管理和[T0]的信息 https://webpack.js.org/guides/dependency-management/.

自动加载 Vuex 模块

有时,当我们在一个大项目中工作时,我们需要管理大量导入的Vuex模块和存储。要处理这些模块,我们始终需要通过创建一个将导入所有文件的文件来导入它们,然后将这些文件导出到 Vuex 存储创建中。

在本教程中,我们将了解一个函数,该函数使用 webpackrequire.context函数自动加载这些文件并将其注入 Vuex 存储创建中。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

我们需要使用Vue-CLI创建一个新的 Vue 项目,或者使用在以前的配方中创建的项目:

  1. 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vuex-import
  1. CLI 将询问一些有助于创建项目的问题。您可以使用箭头键进行导航,输入键继续,空格键选择一个选项。

  2. 启动新项目有两种方法。默认方法是一个基本的babeleslint项目,没有任何插件或配置,Manually模式,您可以选择更多的模式、插件、过梁和选项。我们将选择Manually

? Please pick a preset: (Use arrow keys)
  default (babel, eslint) ❯ Manually select features ‌
  1. 现在,我们被问及该项目需要哪些功能。这些特性是一些 Vue 插件,如VuexRoutervue-router)、测试器、linter 等等。选择BabelVuexLinter / Formatter
? Check the features needed for your project: (Use arrow keys) ❯ Babel
  TypeScript
  Progressive Web App (PWA) Support
Router ❯Vuex
  CSS Pre-processors ❯ Linter / Formatter
  Unit Testing
  E2E Testing
  1. 通过选择 linter 和 formatter 继续此过程。在本例中,我们将选择ESLint + Airbnb配置:
? Pick a linter / formatter config: (Use arrow keys)
  ESLint with error prevention only ❯ ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier
  1. 在设置了 linting 规则之后,我们需要定义它们何时应用于代码。可应用于on save或固定于on commit
? Pick additional lint features: (Use arrow keys)
  Lint on save ❯ Lint and fix on commit
  1. 在定义了所有这些插件、linter 和处理器之后,我们需要选择存储设置和配置的位置。存储它们的最佳位置是在专用文件中,但也可以将它们存储在package.json
? Where do you prefer placing config for Babel, ESLint, etc.? (Use 
  arrow keys) ❯ In dedicated config files 
  In package.json 
  1. 现在,您可以选择是否要将此选择作为未来项目的预设,因此无需重新选择所有内容:
? Save this as a preset for future projects? (y/N) n

Vue-CLI将创建项目,并自动为我们安装软件包。

如果要在安装完成后在vue-ui上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:

> vue ui

也可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的npm命令:

  • npm run serve-在本地运行开发服务器
  • npm run build -构建并缩小部署应用
  • npm run lint -在代码上执行 lint

怎么做。。。

按照以下说明在项目中创建vuex模块的自动导入,该模块将处理特定文件夹中的路由文件:

  1. 创建路由文件并将其放入store文件夹后,我们需要确保每个store文件中都有一个默认的export对象。在index.js文件中,在src/store文件夹中,我们需要提取storesmodules的数组:
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({});
  1. src/store文件夹中创建另一个名为loader.js的文件(这将是我们的module加载器)。重要的是要记住,在使用这个配方时,您将使用vuex名称空间,因为所有stores都需要作为一个模块使用,并且需要在单个 JavaScript 对象中导出。每个文件名都将用作对命名空间的引用,并将其解析为 camelCase 文本样式:
const toCamel = (s) => s.replace(/([-_][a-z])/ig, (c) =>
    c.toUpperCase()
  .replace(/[-_]/g, ''));
const requireModule = require.context('./modules/', false, 
   /^(?!.*test).*\.js$/is);
const modules = {};

requireModule.keys().forEach((fileName) => {
  const moduleName = toCamel(fileName.replace(/(\.\/|\.js)/g, ''));

  modules[moduleName] = {
    namespaced: true,
    ...requireModule(fileName).default,
  };
});

export default modules;
  1. 由于默认情况下我们将导入modules文件夹中的每个文件,因此一个好的做法是为每个模块创建一个文件。例如,当您将创建一个名为user的模块时,您需要创建一个名为user.js的文件,该文件导入所有stores操作、突变、getter 和状态。这些可以放在与模块同名的文件夹中。modules文件夹的结构与此类似:
modules
├── user.js
├── user
│ └── types.js
│   └── state.js
│   └── actions.js
│   └── mutations.js
│   └── getters.js
└───────

src/store/modules文件夹中的user.js文件如下所示:

import state from './user/state';
import actions from './user/actions';
import mutations from './user/mutations';
import getters from './user/getters';

export default {
  state,
  actions,
  mutations,
  getters,
};
  1. src/store文件夹的index.js文件中,我们需要添加自动加载的导入模块:
import Vue from 'vue';
import Vuex from 'vuex';
import modules from './loader';

Vue.use(Vuex);

export default new Vuex.Store({
  modules,
});

现在,当您在src/store/modules文件夹中创建新的.js文件时,您的vuex模块将自动加载到应用中。

它是如何工作的。。。

require.context是一个网页包内置函数,它接收执行搜索的目录、指示此搜索中是否包含子目录的布尔标志,以及文件名模式匹配的正则表达式(都作为参数)。

当构建过程开始时,webpack 将搜索所有的require.context函数,并预执行它们,因此导入所需的文件将在那里进行最终构建。

在我们的例子中,我们为文件夹传递了./modules,为不在子目录中搜索传递了false,为正则表达式传递了/^(?!.*test).*\.js$/is来搜索.js文件,并忽略名称中包含.test的文件。

然后,函数将搜索文件,并通过for循环将结果传递给vuex模块数组中的文件内容。

另见

在[T1]的网页包文档中阅读更多关于网页包依赖关系管理和[T0]的信息 https://webpack.js.org/guides/dependency-management/.

创建自定义指令

谈到诸如 Vue 之类的可视化框架,我们总是考虑组件、渲染和可视化元素,而我们忘记了除了组件本身之外还有很多东西。

有一些指令使组件与模板引擎一起工作,模板引擎是数据和可视化结果之间的绑定代理。Vue 的核心有内置指令,如v-ifv-elsev-for

在本食谱中,我们将学习如何制作指令。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

我们需要使用Vue-CLI创建一个新的 Vue 项目,或者使用在以前的配方中创建的项目:

  1. 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vue-directive
  1. CLI 将询问一些有助于创建项目的问题。您可以使用箭头键导航,输入键继续,使用 s起搏器选择选项。

  2. 启动新项目有两种方法。默认方法是一个基本的babeleslint项目,没有任何插件或配置,Manually模式,您可以选择更多的模式、插件、过梁和选项。我们将选择Manually

? Please pick a preset: (Use arrow keys)
  default (babel, eslint) ❯ Manually select features ‌
  1. 现在,我们被问及我们希望在项目中使用的功能。这些特性是一些 Vue 插件,如VuexRoutervue-router)、测试器、linter 等等。选择BabelLinter / Formatter
? Check the features needed for your project: (Use arrow keys) ❯ Babel
   TypeScript
   Progressive Web App (PWA) SupportRouter
  Vuex
   CSS Pre-processors ❯  Linter / Formatter
   Unit Testing
   E2E Testing
  1. 通过选择 linter 和 formatter 继续此过程。在本例中,我们将选择ESLint + Airbnb配置:
? Pick a linter / formatter config: (Use arrow keys)
  ESLint with error prevention only ❯ ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier
  1. 在设置了 linting 规则之后,我们需要定义它们何时应用于代码。可应用于on save或固定于on commit
? Pick additional lint features: (Use arrow keys)
  Lint on save ❯ Lint and fix on commit
  1. 在定义了所有这些插件、linter 和处理器之后,我们需要选择存储设置和配置的位置。存储它们的最佳位置是在专用文件中,但也可以将它们存储在package.json
? Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys) ❯ In dedicated config files 
  In package.json 
  1. 现在,您可以选择是否要将此选择设置为未来项目的预设,以便不必重新选择所有内容:
? Save this as a preset for future projects? (y/N) n

Vue-CLI将创建项目,并自动为我们安装软件包。

如果要在安装完成后在vue-ui上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:

> vue ui

或者,您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的 npm 命令:

  • npm run serve-在本地运行开发服务器
  • npm run build -构建并缩小部署应用
  • npm run lint -在代码上执行 lint

怎么做。。。

按照以下说明为屏蔽输入字段创建指令:

  1. src/directives文件夹中创建一个名为formMaskInputDirective.js的文件,在同一文件夹中创建一个名为tokens.js的文件。

  2. tokens.js文件中,我们将添加我们的面具基础标记。这些标记将用于标识我们的输入将接受的价值类型:

export default {
  "#": { pattern: /[\x2A\d]/ },
  0: { pattern: /\d/ },
  9: { pattern: /\d/ },
  X: { pattern: /[0-9a-zA-Z]/ },
  S: { pattern: /[a-zA-Z]/ },
  A: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleUpperCase() },
  a: { pattern: /[a-zA-Z]/, transform: v => v.toLocaleLowerCase() },
  "!": { escape: true }
};
  1. 我们从token.js导入代币并创建我们的功能:
import tokens from './tokens';

function maskerValue() {
  // Code will be developed in this recipe
}

function eventDispatcher() {
  // Code will be developed in this recipe
}

function maskDirective() {
 // Code will be developed in this recipe
}

export default maskDirective;
  1. maskDirective函数中,我们需要检查指令被调用方传递的指令上的绑定值,并检查它是否为有效绑定。为此,我们将首先检查binding参数上是否存在value属性,然后使用导入的tokens将其添加到config变量中:
function maskDirective(el, binding) {
  let config;

  if (!binding.value) return false;

  if (typeof config === 'string') {
    config = {
      mask: binding.value,
      tokens,
    };
  } else {
    throw new Error('Invalid input entered');
  }
  1. 现在我们需要检查元素并验证它是否是一个inputHTML 元素。为此,我们将检查该指令传递的元素是否具有tagNameinput,如果没有,我们将尝试在传递的元素中找到inputHTML 元素:
let element = el;

  if (element.tagName.toLocaleUpperCase() !== 'INPUT') {
    const els = element.getElementsByTagName('input');

    if (els.length !== 1) {
      throw new Error(`v-input-mask directive requires 1 input, 
         found ${els.length}`);
    } else {
      [element] = els;
    }
  }
  1. 现在我们需要在元素的输入中添加一个事件侦听器。侦听器将调用两个外部函数,一个用于调度事件,另一个用于将屏蔽值返回到输入:
element.oninput = (evt) => {
    if (!evt.isTrusted) return;
    let position = element.selectionEnd;

    const digit = element.value[position - 1];
    element.value = maskerValue(element.value, config.mask, 
       config.tokens);
    while (
      position < element.value.length
      && element.value.charAt(position - 1) !== digit
    ) {
      position += 1;
    }
    if (element === document.activeElement) {
      element.setSelectionRange(position, position);
      setTimeout(() => {
        element.setSelectionRange(position, position);
      }, 0);
    }
    element.dispatchEvent(eventDispatcher('input'));
  };

  const newDisplay = maskerValue(element.value, config.mask, 
     config.tokens);
  if (newDisplay !== element.value) {
    element.value = newDisplay;
    element.dispatchEvent(eventDispatcher('input'));
  }

  return true;
}
// end of maskDirective function
  1. 让我们创建eventDispatcher函数;此函数将发出v-on指令将监听的事件:
function eventDispatcher(name) {
  const evt = document.createEvent('Event');

  evt.initEvent(name, true, true);

  return evt;
}
  1. 现在是复杂的部分:将屏蔽输入值返回到输入。为此,我们需要创建maskerValue函数。此函数接收值、掩码和令牌作为参数。函数根据掩码检查当前值,以查看掩码是否完整或值是否为有效标记。如果一切正常,它将把值传递给输入:
function maskerValue(v, m, tkn) {
  const value = v || '';

  const mask = m || '';

  let maskIndex = 0;

  let valueIndex = 0;

  let output = '';

  while (maskIndex < mask.length && valueIndex < value.length) {
    let maskCharacter = mask[maskIndex];
    const masker = tkn[maskCharacter];
    const valueCharacter = value[valueIndex];

    if (masker && !masker.escape) {
      if (masker.pattern.test(valueCharacter)) {
        output += masker.transform ? 
           masker.transform(valueCharacter) : valueCharacter;
        maskIndex += 1;
      }

      valueIndex += 1;
    } else {
      if (masker && masker.escape) {
        maskIndex += 1;
        maskCharacter = mask[maskIndex];
      }

      output += maskCharacter;

      if (valueCharacter === maskCharacter) valueIndex += 1;

      maskIndex += 1;
    }
  }

  let outputRest = '';
  while (maskIndex < mask.length) {
    const maskCharacter = mask[maskIndex];

    if (tkn[maskCharacter]) {
      outputRest = '';
      break;
    }

    outputRest += maskCharacter;

    maskIndex += 1;
  }

  return output + outputRest;
}
//end of maskerValue function
  1. 文件准备就绪后,我们需要在main.js文件中导入掩码指令,并将该指令添加到 Vue 中,使该指令名为'input-mask'
import Vue from 'vue';
import App from './App.vue';
import InputMaskDirective from './directives/formMaskInputDirective';

Vue.config.productionTip = false;

Vue.directive('input-mask', InputMaskDirective);

new Vue({
  render: (h) => h(App),
}).$mount('#app');
  1. 要在我们的应用中使用该指令,我们需要在单个文件组件<template>节中的inputHTML 元素上调用该指令,并在v-input-mask指令中传递token模板'###-###-###',如下所示:
<template>
  <div id="app">
    <input
      type="text"
      v-input-mask="'###-###-###'"
      v-model="inputMask"
    />
  </div>
</template>

<script>
export default {
  name: 'app',
  data: () => ({
    inputMask: '',
  }),
};
</script>

它是如何工作的。。。

Vue 指令有五个可能的挂钩。我们只用了一个,bind。它直接绑定到元素和组件。它有三个参数:elementbindingvnode

当我们将main.js文件中的指令添加到 Vue 时,我们使它在应用中的任何地方都可用,因此该指令已经位于App.vue供输入使用。

同时我们在输入元素上调用v-input-mask,我们将第一个参数element传递给指令,第二个参数binding是属性的值。

我们的指令通过检查输入上的每个新字符值来工作。执行正则表达式测试并验证该字符,以查看它是否是指令实例化中给定的令牌列表上的有效字符。然后,如果通过测试,则返回该字符;如果该字符无效,则不返回任何内容。

创建 Vue 插件

有时需要向应用添加新的内容,并且需要共享此内容。最好的分享方式是使用插件。在 Vue 中,插件是 Vue 全局原型的补充,它使用新功能(如指令、混合、过滤器、原型注入或全新功能)扩展初始化的应用。

现在,我们将学习如何制作插件,以及如何使用插件与 Vue 整体交互(而不会弄乱原型并破坏它)。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

我们需要使用Vue-CLI创建一个新的 Vue 项目,或者使用在以前的配方中创建的项目:

  1. 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create vue-plugin
  1. CLI 将询问一些有助于创建项目的问题。您可以使用箭头键导航,输入键继续,使用 s起搏器选择选项。

  2. 启动新项目有两种方法。默认方法是一个基本的babeleslint项目,没有任何插件或配置,Manually模式,您可以选择更多的模式、插件、过梁和选项。我们将选择Manually

? Please pick a preset: (Use arrow keys)
  default (babel, eslint) ❯ Manually select features ‌
  1. 现在,我们被问及我们希望在项目中使用的功能。这些特性是一些 Vue 插件,如VuexRoutervue-router)、测试器、linter 等等。选择BabelLinter / Formatter
? Check the features needed for your project: (Use arrow keys) ❯  Babel
   TypeScript
   Progressive Web App (PWA) Support
Router
  Vuex
   CSS Pre-processors ❯  Linter / Formatter
   Unit Testing
   E2E Testing
  1. 通过选择 linter 和 formatter 继续此过程。在本例中,我们将选择ESLint + Airbnb配置:
? Pick a linter / formatter config: (Use arrow keys)
  ESLint with error prevention only ❯ ESLint + Airbnb config 
  ESLint + Standard config 
  ESLint + Prettier
  1. 在设置了 linting 规则之后,我们需要定义它们何时应用于代码。可应用于on save或固定于on commit
? Pick additional lint features: (Use arrow keys)
   Lint on save ❯  Lint and fix on commit
  1. 在定义了所有这些插件、linter 和处理器之后,我们需要选择存储设置和配置的位置。存储它们的最佳位置是在专用文件中,但也可以将它们存储在package.json
? Where do you prefer placing config for Babel, ESLint, etc.? (Use
  arrow keys) ❯ In dedicated config files 
  In package.json 
  1. 现在,您可以选择是否要将此选择作为未来项目的预设,因此无需重新选择所有内容:
? Save this as a preset for future projects? (y/N) n

Vue-CLI将创建项目,并自动为我们安装软件包。

如果要在安装完成后在vue-ui上检查项目,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),并执行以下命令:

> vue ui

或者,您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令之一来运行内置的 npm 命令:

  • npm run serve-在本地运行开发服务器
  • npm run build -构建并缩小部署应用
  • npm run lint -在代码上执行 lint

怎么做。。。

编写 Vue 插件很简单,无需了解更多关于 Vue 本身的信息。插件的基本概念是一个需要有install函数的对象,当Vue.use()方法调用该函数时将执行该函数。install函数将接收两个参数:Vue 和用于实例化插件的选项。

按照以下说明编写插件,为 Vue 全局原型添加两个新功能$localStorage$sessionStorage

  1. 在我们的项目中,我们需要在名为storageManipulator.jssrc/plugin文件夹中创建一个文件。
  2. 在此文件中,我们将创建插件安装对象–我们将添加默认插件选项和功能的基本原型:
/* eslint no-param-reassign: 0 */

const defaultOption = {
  useSaveFunction: true,
  useRetrieveFunction: true,
  onSave: value => JSON.stringify(value),
  onRetrieve: value => JSON.parse(value),
};

export default {
  install(Vue, option) {
    const baseOptions = {
      ...defaultOption,
      ...option,
    };

    Vue.prototype.$localStorage = generateStorageObject(
       window.localStorage,
        baseOptions,
      ); // We will add later this code

    Vue.prototype.$sessionStorage = generateStorageObject(
        window.localStorage,
        baseOptions,
      ); // We will add later this code
  },
};
  1. 现在我们需要创建generateStorageObject函数。此函数将接收两个参数:第一个是窗口存储对象,第二个是插件选项。这样,就可以生成将注入 Vue 的原型上使用的对象:
const generateStorageObject = (windowStorage, options) => ({
  set(key, value) {
    windowStorage.setItem(
      key,
      options.useSaveFunction
        ? options.onSave(value)
        : value,
    );
  },

  get(key) {
    const item = windowStorage.getItem(key);
    return options.useRetrieveFunction ? options.onRetrieve(item) : 
      item;
  },

  remove(key) {
    windowStorage.removeItem(key);
  },

  clear() {
    windowStorage.clear();
  },
});
  1. 我们需要将插件导入到main.js中,然后使用Vue.use功能将插件安装到我们的 Vue 应用中:
import Vue from 'vue';
import App from './App.vue';
import StorageManipulatorPlugin from './plugin/storageManipulator';

Vue.config.productionTip = false;

Vue.use(StorageManipulatorPlugin);

new Vue({
  render: h => h(App),
}).$mount('#app');

现在,您可以在 Vue 应用中的任何位置使用该插件,调用this.$localStorage方法或this.$sessionStorage

它是如何工作的。。。

Vue 插件的工作原理是将指示要使用的所有代码添加到 Vue 应用层(如 mixin)。

当我们使用Vue.use()导入插件时,我们告诉 Vue 调用导入文件对象上的install()函数并执行它。Vue 将自动将当前 Vue 作为第一个参数传递,并将选项(如果已声明)作为第二个参数传递。

在我们的插件中,当调用install()函数时,我们首先创建baseOptions,将默认选项与传递的参数合并,然后向 Vue 原型中注入两个新属性。这些属性现在随处可见,因为传递的Vue参数是应用中使用的Vue global

我们的generateStorageObject是浏览器存储 API 的纯粹抽象。我们将其用作插件中原型的生成器。

另见

有关 Vue 插件的更多信息,请访问https://vuejs.org/v2/guide/plugins.html.

你可以在找到一份精心策划的 Vue 插件列表 https://github.com/vuejs/awesome-vue.

利用类星体在 Vue 中创建 SSR、SPA、PWA、Cordova 和电子应用

Quasar 是一个基于 Vue 和材料设计的框架,它利用了“一次编写,到处使用”的优势

CLI 可以将相同的代码库部署到不同的风格,例如单页应用SPA)、服务器端渲染SSR)、渐进式 Web 应用PWA移动应用(Cordova)和桌面应用(电子版)。

这就消除了开发人员的一些问题,例如使用HMR热模块重新加载)配置 webpack、Cordova 和 Electron 进行开发,或者在 SPA 项目中添加 SSR 配置。该框架帮助开发人员尽快开始生产。

在本教程中,我们将学习如何使用 Quasar 和 CLI 创建基本项目,以及如何使用 CLI 添加 SPA、PWA、SSR、移动应用和桌面应用的开发目标。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @quasar/cli

我们需要使用 Quasar CLI 创建一个新的 Quasar 项目,或者使用在以前的配方中创建的项目。

为此,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> quasar create quasar-project

现在,当被询问时,我们需要选择手动选择功能:

  1. Quasar-CLI将询问您项目名称。定义项目名称。在我们的案例中,我们选择quasar_project
> Project name: quasar_project
  1. 然后Quasar-CLI将询问项目产品名称。这将由移动应用用于定义其标题名称。在我们的案例中,我们使用提供的默认名称:
> Project product name (must start with letter if building mobile 
    apps) (Quasar App)
  1. 现在Quasar-CLI将要求提供项目说明。当共享页面时,这用于搜索引擎中的元标记。在我们的案例中,我们使用了提供的默认描述:
> Project description: (A Quasar Framework app)
  1. 然后Quasar-CLI将询问项目作者。请在此填写一个package.json有效名称(例如Heitor Ribeiro<heitor@example.com>
> Author: <You>
  1. 现在是选择 CSS 预处理器的时候了。在我们的情况下,我们将使用Sass with indented syntax
Pick your favorite CSS preprocessor: (can be changed later) (Use arrow keys) ❯ Sass with indented syntax (recommended)
 Sass with SCSS syntax (recommended)
 Stylus
 None (the others will still be available)
  1. 然后Quasar-CLI将询问组件和指令的进口策略。我们将使用默认的auto-import策略:
Pick a Quasar components & directives import strategy: (can be   
  changed later) (Use arrow keys ) ❯ * Auto-import in-use Quasar components & directives - also 
 treeshakes Quasar; minimum bundle size
 * Import everything from Quasar - not treeshaking Quasar;         
    biggest bundle size
  1. 现在我们需要为项目选择额外的功能。我们将选择EsLint
Check the features needed for your project: EsLint
  1. 之后,Quasar-CLI将要求为 ESLint 设置预设。选择Airbnb预设:
Pick an ESLint preset: Airbnb
  1. 最后,Quasar-CLI将请求您要使用的应用来安装项目的依赖项。在我们的例子中,我们使用了yarn,因为我们已经安装了它(但您可以选择您喜欢的一个):
Continue to install project dependencies after the project has been 
  created? (recommended) (Use arrow keys) ❯ Yes, use Yarn (recommended)
 Yes, use npm
 No, I will handle that myself

现在在 IDE 或代码编辑器中打开创建的文件夹。

怎么做。。。

当使用 Quasar 创建应用时,您总是需要选择一种风格来启动,但主代码将是 SPA。因此,其他口味的人会根据他们的需求提供特殊的食物和美味,但是您可以根据构建环境对构建进行个性化设置并使构建执行一些代码。

开发 SPA(单页应用)

SPA 是开箱即用的解决方案;无需添加任何新配置。

让我们开始向应用添加一个新页面。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> quasar new page About

Quasar-CLI将自动为我们创建 Vue 页面。我们需要在路由文件中添加对页面的引用,该页面将在应用上可用:

  1. 为此,我们需要打开src/router文件夹中的routes.js文件,并添加About页面:
const routes = [
  {
    path: '/',
    component: () => import('layouts/MainLayout.vue'),
    children: [
      { path: '', name: 'home', component: () => 
         import('pages/Index.vue') },
      { path: 'about', name: 'about', component: () => 
         import('pages/About.vue') },
    ],
  },
  {
    path: '*',
    component: () => import('pages/Error404.vue'),
  }
];

export default routes;
  1. 然后打开src/pages文件夹中的About.vue文件。您会发现该文件是一个单一的文件组件,其中有一个空的QPage组件,因此我们需要在<template>部分添加一个基本的标题和页面指示:
<template>
<q-page
    padding
    class="flex flex-start"
  >
    <h1 class="full-width">About</h1>
    <h2>This is an About Us Page</h2>
  </q-page>
</template>

<script>
export default {
  name: 'PageAbout',
};
</script>
  1. 现在,在MainLayout.vue文件的src/layouts文件夹中,到q-drawer组件,我们需要添加到HomeAbout页面的链接:
<template>
  <q-layout view="lHh Lpr lFf">
    <q-header elevated>
      <q-toolbar>
        <q-btn flat dense round 
        @click="leftDrawerOpen = !leftDrawerOpen" 
        aria-label="Menu">
          <q-icon name="menu" />
        </q-btn>

        <q-toolbar-title>
          Quasar App
          </q-toolbar-title>

        <div>Quasar v{{ $q.version }}</div>
      </q-toolbar>
    </q-header>

    <q-drawer v-model="leftDrawerOpen" 
    bordered content-class="bg-grey-2">
      <q-list>
        <q-item-label header>Menu</q-item-label>
        <q-item clickable tag="a" :to="{name: 'home'}">
          <q-item-section avatar>
            <q-icon name="home" />
          </q-item-section>
          <q-item-section>
            <q-item-label>Home</q-item-label>
          </q-item-section>
        </q-item>
        <q-item clickable tag="a" :to="{name: 'about'}">
          <q-item-section avatar>
            <q-icon name="school" />
          </q-item-section>
          <q-item-section>
            <q-item-label>About</q-item-label>
          </q-item-section>
        </q-item>
      </q-list>
    </q-drawer>

    <q-page-container>
      <router-view />
    </q-page-container>
  </q-layout>
</template>

<script>
export default {
  name: "MyLayout",
  data() {
    return {
      leftDrawerOpen: this.$q.platform.is.desktop
    };
  }
};
</script>

我们以一个在类星体框架内运行的 SPA 的简单示例结束。

命令

您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下操作之一来运行Quasar-CLI命令:

  • quasar dev-启动开发模式
  • quasar build-建造水疗中心

开发 PWA(渐进式 Web 应用)

为了开发 PWA,我们首先需要通知 Quasar,我们想要添加一种新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> quasar mode add pwa

Quasar-CLI将创建一个名为src-pwa的文件夹,其中包含我们的service-workers文件,与主代码分离。

要清理新添加的文件并将其转换为 Airbnb 格式,我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows),然后执行以下命令:

> eslint --fix --ext .js ./src-pwa

我们添加到 SPA 的代码仍将用作我们的基础,以便我们也可以添加新的页面、组件和其他功能,这些功能将在 PWA 上使用。

So, are you wondering why service-worker is not in the main src folder? This is because those files are exclusively for PWAs, and are not needed in any other case than this one. The same will happen in different build types, such as Electron, Cordova, and SSR.

在 PWA 上配置 quasar.conf

对于 PWA 开发,您可以在root文件夹中的quasar.conf.js文件上设置一些特殊标志:

pwa: {
  // workboxPluginMode: 'InjectManifest',
  // workboxOptions: {},
  manifest: {
    // ...
  },

  // variables used to inject specific PWA
  // meta tags (below are default values)
  metaVariables: {
    appleMobileWebAppCapable: 'yes',
    appleMobileWebAppStatusBarStyle: 'default',
    appleTouchIcon120: 'statics/icons/apple-icon-120x120.png',
    appleTouchIcon180: 'statics/icons/apple-icon-180x180.png',
    appleTouchIcon152: 'statics/icons/apple-icon-152x152.png',
    appleTouchIcon167: 'statics/icons/apple-icon-167x167.png',
    appleSafariPinnedTab: 'statics/icons/safari-pinned-tab.svg',
    msapplicationTileImage: 'statics/icons/ms-icon-144x144.png',
    msapplicationTileColor: '#000000'
  }
}

命令

您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下操作之一来运行Quasar-CLI命令:

  • quasar dev -m pwa-作为 PWA 启动开发模式
  • quasar build -m pwa-将代码构建为 PWA

开发 SSR(服务器端渲染)

为了发展 SSR,我们首先需要通知 Quasar,我们想增加一种新的发展模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> quasar mode add ssr

Quasar-CLI将创建一个名为src-ssr的文件夹,其中包含我们的extensionserver启动程序文件,与主代码分离。

extension文件不是由babel传输的,而是在 Node.js 上下文上运行的,因此它与 Express 或Nuxt.js应用的环境相同。您可以使用服务器插件,例如databasefilereadfilewrites

server起始文件将是src-ssr文件夹中的index.js文件。作为扩展,不被babel传输,在 Node.js 上下文上运行。对于 HTTP 服务器,它使用 Express,如果您配置quasar.conf.js向客户端传递一个 PWA,您可以同时拥有一个带 PWA 的 SSR。

在 SSR 上配置 quasar.conf

对于 SSR 开发,您可以在root文件夹中的quasar.conf.js文件上配置一些特殊标志:

ssr: {
  pwa: true/false, // should a PWA take over (default: false), or just 
                                                            // a SPA?
},

命令

您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下操作之一来运行Quasar-CLI命令:

  • quasar dev -m ssr-作为 SSR 启动开发模式
  • quasar build -m ssr-将代码构建为 SSR
  • quasar serve-运行 HTTP 服务器(可用于生产)

开发移动应用(Cordova)

为了发展 SSR,我们首先需要通知 Quasar,我们想增加一种新的发展模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> quasar mode add cordova

现在Quasar-CLI将询问您一些配置问题:

  1. Cordova 应用 ID 是什么?(org.cordova.quasar.app)

  2. Cordova 是否可以匿名报告使用统计数据,以便随着时间的推移改进该工具?(是/否)N

Quasar-CLI将创建一个名为src-cordova的文件夹,其中将包含一个 Cordova 项目。

**Cordova 项目的文件夹结构如下所示:

src-cordova/
├── config.xml
├── packages.json
├── cordova-flag.d.ts
├── hooks/
├── www/
├── platforms/
├── plugins/

As a separate project inside Quasar, to add Cordova plugins, you need to call plugman or cordova plugin add command inside the src-cordova folder.

在 Cordova 上配置 quasar.conf

对于 Cordova 开发,您可以在root文件夹中的quasar.conf.js文件上设置一些特殊标志:

cordova: {         
  iosStatusBarPadding: true/false, // add the dynamic top padding on 
     // iOS mobile devices
  backButtonExit: true/false // Quasar handles app exit on mobile phone 
      // back button       
},

命令

您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下操作之一来运行Quasar-CLI命令:

If you don't have a Cordova environment already configured on your desktop, you can find more information on how to set it up here: https://quasar.dev/quasar-cli/developing-cordova-apps/preparation#Android-setup.

  • quasar dev -m cordova -T android-作为 Android 设备模拟器启动开发模式
  • quasar build -m cordova -T android-将代码构建为 Android
  • quasar dev -m cordova -T ios-作为 iOS 设备模拟器启动开发模式(仅限 macOS)
  • quasar build -m cordova -T ios-作为 iOS 设备模拟器启动构建模式(仅限 macOS)

开发桌面应用(Electron)

要开发 SSR,我们首先需要通知 Quasar,我们想要添加一种新的开发模式。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> quasar mode add electron

Quasar-CLI将创建一个名为src-electron的文件夹,其中将包含一个电子项目。

Electron 项目的文件夹结构如下所示:

src-electron/
├── icons/
├── main-process/
├── electron-flag.d.ts

icons文件夹中,您将找到electron-packager在构建项目时将使用的图标。在main-process文件夹中将是您的主要电子文件,拼接成两个文件:一个仅在开发时调用,另一个在开发和生产时调用。

在 Electron 上配置 quasar.conf

对于 Electron 开发,您可以在根文件夹的quasar.conf.js文件上设置一些特殊标志:

electron: {
  // optional; webpack config Object for
  // the Main Process ONLY (/src-electron/main-process/)
  extendWebpack (cfg) {
    // directly change props of cfg;
    // no need to return anything
  },

  // optional; EQUIVALENT to extendWebpack() but uses webpack-chain;
  // for the Main Process ONLY (/src-electron/main-process/)
  chainWebpack (chain) {
    // chain is a webpack-chain instance
    // of the Webpack configuration
  },

  bundler: 'packager', // or 'builder'

  // electron-packager options
  packager: {
    //...
  },

  // electron-builder options
  builder: {
    //...
  }
},

The packager key uses the API options for the electron-packager module, and the builder key uses the API options for the electron-builder module.

命令

您可以通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下操作之一来运行Quasar-CLI命令:

  • quasar dev -m electron-以电子方式启动开发模式
  • quasar build -m electron-将代码构建为 Electron

它是如何工作的。。。

这一切都是可能的,因为 Quasar 框架在 CLI 上为您封装了构建、解析和绑定。您不必担心 Electron、Cordova 甚至 Babel 的网页包和配置。

一个简单的 CLI 命令可以为您生成一个全新的页面、布局、组件、存储、路由,甚至是一个新的构建。由于 CLI 只是 Vue、webpack、Babel 和其他工具的包装,因此您不必只使用 Quasar visual 组件。如果不想使用它们,可以不导入它们并使用 CLI 的强大功能构建应用。

另见

您可以在的文档中查看更多关于类星体框架的信息 https://quasar.dev/introduction-to-quasar.

阅读更多关于类星体 SPA 开发的信息 https://quasar.dev/quasar-cli/developing-spa/introduction.

阅读更多关于类星体 PWA 发展的信息 https://quasar.dev/quasar-cli/developing-pwa/introduction.

阅读更多关于类星体 SSR 发展的信息 https://quasar.dev/quasar-cli/developing-ssr/introduction.

阅读更多关于使用 Quasar 开发移动应用的信息 https://quasar.dev/quasar-cli/developing-cordova-apps/introduction.

更多关于科尔多瓦项目的信息,请访问https://cordova.apache.org.

上阅读更多关于使用 Quasar 开发桌面应用的信息 https://quasar.dev/quasar-cli/developing-electron-apps/introduction.

上阅读更多关于电子项目的信息 https://electronjs.org/.

更多关于electron-packager的信息,请访问https://github.com/electron/electron-packager.

找到electron-packager选项 APIhttps://electron.github.io/electron-packager/master/interfaces/electronpackager.options.html.

阅读更多关于“T0”的信息,参见“T1”,https://www.electron.build/. “T2”。

找到电子构建选项 APIhttps://www.electron.build/configuration/configuration.

创建更智能的 Vue 监视程序和计算属性

在 Vue 中,使用监视程序和计算属性始终是检查和缓存数据的最佳解决方案,但有时这些数据需要特殊处理,或者需要以与预期不同的方式进行操作。有一些方法可以赋予这些 Vue API 新的生命,帮助您的开发和生产效率。

怎么做。。。

我们将此配方分为两类:一类用于观察者,另一类用于计算属性。一些方法通常一起使用,例如non-cached计算值和deep-watched值。

观察者

选择这三个观察者配方是为了提高生产率和最终代码质量。使用这些方法可以减少代码重复并提高代码重用。

使用方法名

所有观察者都可以接收方法名而不是函数,从而防止您编写重复的代码。这将帮助您避免重新编写相同的代码,或检查值并将其传递给函数:

<script>
export default {
  watch: {
    myField: 'myFunction',
  },
  data: () => ({
    myField: '',
  }),
  methods: {
    myFunction() {
      console.log('Watcher using method name.');
    },
 },
};
</script>

即时通话和深入倾听

您可以通过立即传递一个属性,将您的观察者设置为创建后立即执行,并通过调用deep属性,使其无论值的变化深度如何都能执行:

<script>
export default {
  watch: {
    myDeepField: {
      handler(newVal, oldVal) {
        console.log('Using Immediate Call, and Deep Watch');
        console.log('New Value', newVal);
        console.log('Old Value', oldVal);
      },
      deep: true,
      immediate: true,
    },
  },
  data: () => ({
    myDeepField: '',
  }),
};
</script>

多处理器

您可以让您的观察者同时执行各种处理程序,而无需将观察处理程序设置为绑定到唯一的函数:

<script>
export default {
  watch: {
    myMultiField: [
      'myFunction',
      {
        handler(newVal, oldVal) {
          console.log('Using Immediate Call, and Deep Watch');
          console.log('New Value', newVal);
          console.log('Old Value', oldVal);
        },
        immediate: true,
      },
    ],
  },
  data: () => ({
    myMultiField: '',
  }),
  methods: {
    myFunction() {
      console.log('Watcher Using Method Name');
    },
  },
};
</script>

计算

有时,计算属性只是用作简单的基于缓存的值,但它们的功能更强。这里有两种方法展示了如何提取这种能量。

没有缓存值

通过将cache属性设置为false,您可以将计算属性设置为始终更新的值,而不是缓存的值:

<script>
export default {
  computed: {
    field: {
      get() {
       return Date.now();
      },
      cache: false,
   },
  },
};
</script>

接二连三

您可以向计算属性添加 setter 函数,使其成为完全完整的数据属性,但不绑定到数据。

不建议这样做,但这是可能的,在某些情况下,您可能需要这样做。例如,必须以毫秒为单位保存日期,但需要以 ISO 格式显示。使用此方法,您可以拥有dateIso属性getset值:

<script>
export default {
  data: () => ({
    dateMs: '',
  }),
  computed: {
    dateIso: {
      get() {
        return new Date(this.dateMs).toISOString();
      },
      set(v) {
        this.dateMs = new Date(v).getTime();
      },
   },
  },
};
</script>

另见

有关 VuewatchAPI 的更多信息,请访问https://vuejs.org/v2/api/#watch.

有关 VuecomputedAPI 的更多信息,请访问https://vuejs.org/v2/api/#computed.

使用 Python Flask 作为 API 创建 Nuxt.js SSR

Nuxt.js是一个服务器端渲染框架,用于渲染服务器上的所有内容并将其加载。通过这个过程,页面可以在呈现之前获得 SEO 和快速 API 获取的功能。

正确地使用它,您可以实现强大的 SPA 或 PWA 以及以前无法实现的其他功能。

在后端,Python 是一种快速稳定的解释型动态语言。有了活跃的用户群和快速的学习曲线,这非常适合服务器 API。

将两者结合在一起,就有可能以尽可能快的速度部署一个强大的应用。

准备‌

此配方的先决条件如下:

  • Node.js 12+
  • python

所需的 Node.js 全局对象如下:

  • create-nuxt-app

要安装create-nuxt-app,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> npm install -g create-nuxt-app

对于这个配方的后端,我们将使用Python。此配方所需的 Python 全局对象如下:

  • flask
  • flask-restful
  • flask-cors

要安装flaskflask-restfulflask-cors,您需要在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)中执行以下命令:

> pip install flask
> pip install flask-restful
> pip install flask-cors

怎么做。。。

我们需要把食谱分成两部分。第一部分是后端部分(或者 API,如果您愿意的话),将使用 Python 和 Flask 完成。第二部分为前端部分,在Nuxt.js上以 SSR 模式运行。

创建烧瓶 API

我们的 API 服务器将基于 Python Flask 框架。我们需要创建一个服务器文件夹来存储我们的服务器文件,并开始服务器的开发。

您将需要安装以下 Python 软件包。为此,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

  • 要安装烧瓶框架,请使用以下命令:
> pip install flask
  • 要安装 Flask RESTful 扩展,请使用以下命令:
> pip install flask-restful
  • 要安装烧瓶 CORS 扩展,请使用以下命令:
> pip install flask-cors

初始化应用

要创建简单的 RESTful API,我们将创建一个文件并使用 SQLite3 作为数据库:

  1. 创建名为server的文件夹,并在其中创建名为app.py的文件:
import sqlite3 as sql
from flask import Flask
from flask_restful import Resource, Api, reqparse
from flask_cors import CORS

app = Flask(__name__)
api = Api(app)
CORS(app)

parser = reqparse.RequestParser()

conn = sql.connect('tasks.db')
conn.execute('CREATE TABLE IF NOT EXISTS tasks (id INTEGER PRIMARY 
   KEY AUTOINCREMENT, task TEXT)')
conn.close()
  1. 然后,我们将创建我们的ToDo类,在该类的构造函数上,我们将连接到数据库并选择所有tasks
class ToDo(Resource):
    def get(self):
        con = sql.connect('tasks.db')
        cur = con.cursor()
        cur.execute('SELECT * from tasks')
        tasks = cur.fetchall()
        con.close()

        return {
            'tasks': tasks
        }
  1. 为了实现 RESTful POST 方法,创建一个函数,该函数接收task作为参数,并将添加了task的对象、添加的status添加到任务列表中,然后将列表返回给用户:
 def post(self):
        parser.add_argument('task', type=str)
        args = parser.parse_args()

        con = sql.connect('tasks.db')
        cur = con.cursor()
        cur.execute('INSERT INTO tasks(task) values ("
                       {}")'.format(args['task']))
        con.commit()
        con.close()

        return {
            'status': True,
            'task': '{} added.'.format(args['task'])
        }
  1. 接下来,我们将通过创建一个函数来创建 RESTful PUT 方法,该函数将接收[T0]和[T1]作为函数的参数。然后,此功能将用当前的id更新task,并将更新后的task和更新后的status返回给用户:
def put(self, id):
        parser.add_argument('task', type=str)
        args = parser.parse_args()

        con = sql.connect('tasks.db')
        cur = con.cursor()
        cur.execute('UPDATE tasks set task = "{}" WHERE id = 
           {}'.format(args['task'], id))
        con.commit()
        con.close()

        return {
            'id': id,
            'status': True,
            'task': 'The task {} was updated.'.format(id)
        }
  1. 然后,通过创建一个函数来创建一个 RESTful 删除方法,该函数将接收将被删除的taskID,然后将被删除的IDstatustask返回给用户:
 def delete(self, id):
        con = sql.connect('tasks.db')
        cur = con.cursor()
        cur.execute('DELETE FROM tasks WHERE id = {}'.format(id))
        con.commit()
        con.close()

        return {
            'id': id,
            'status': True,
            'task': 'The task {} was deleted.'.format(id)
        }
  1. 最后,我们将ToDo类作为资源添加到'/'路由上的 API 中,并初始化应用:
api.add_resource(ToDo, '/', '/<int:id>')

if __name__ == '__main__':
    app.run(debug=True)

启动服务器

要启动服务器,需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> python server/app.py

您的服务器将在http://localhost:5000上运行并侦听。

创建您的 Nuxt.js 服务器

要呈现应用,您需要创建Nuxt.js应用。使用Nuxt.js``create-nuxt-appCLI,我们将创建它并为其选择一些选项。打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> create-nuxt-app client

然后,将向您询问有关安装过程的一些问题。我们将使用以下方法:

  1. 当您开始使用Nuxt-CLI创建项目时,它将首先询问项目名称。在本例中,我们将选择client作为名称:
Project Name: client
  1. 然后,您需要选择将在项目中使用的编程语言。我们将选择JavaScript
> Programming language: (Use arrow keys)
   ❯ JavaScript
 TypeScript
  1. 接下来,Nuxt-CLI将请求用于安装依赖项的包管理器。在我们的案例中,我们选择Yarn,但您可以选择您喜欢的:
> Package manager: (Use arrow keys) 
   ❯ Yarn
 npm
  1. 现在,Nuxt-CLI将要求在项目中使用 UI 框架。从可用列表中选择Bulma
> UI Framework: Bulma
  1. 然后,Nuxt-CLI将询问您是否要为项目选择额外的模块。我们将从当前模块列表中选择Axios
> Nuxt.JS modules: Axios
  1. Nuxt-CLI将要求我们在项目中使用的起绒工具;我们将选择None
> Choose Linting tools: None
  1. 然后,Nuxt-CLI将询问我们想要在我们的项目上实现的测试框架;我们将选择None
> Choose Test Framework: None
  1. 接下来,Nuxt-CLI将询问项目将使用的渲染模式;我们将选择Universal (SSR)
> Choose Rendering Mode: Universal (SSR)
  1. Nuxt-CLI将询问将在建筑结构上使用的部署目标;我们将选择Server (Node.js hosting)
> Deployment target: Server (Node.js hosting)
  1. 最后,Nuxt-CLI将询问我们想要使用的开发工具配置;我们将选择jsconfig.json
> Development tools: jsconfig.json

CLI 完成安装过程后,我们可以在编辑器或 IDE 上打开client文件夹。

将 Bulma 添加到全局 CSS

要将 Bulma 添加到应用中,我们需要通过执行以下操作在nuxt配置文件中声明它:

  1. 打开client文件夹中的nuxt.config.js,
  2. 然后,更新 CSS 属性并添加 Bulma 导入,以使其在应用的全局范围内可用:
export default {
  /* We need to change only the css property for now, */
  /* the rest we will maitain the same */
  /*
   ** Global CSS
   */
  css: ['bulma/css/bulma.css'],
}

配置 axios 插件

要开始创建 API 调用,我们需要在应用中添加axios插件:

  1. 为此,我们需要打开根文件夹中的nuxt.config.js,文件,并添加axios属性:
export default {
  /* We need to change only the axios property for now, */
  /* the rest we will maitain the same */
  axios: {},
}
  1. axios属性上,添加以下配置属性:
    • HOST定义为'127.0.0.1'
    • PORT定义为'5000'
    • https定义为false
    • debug定义为true
axios: {
  HOST: '127.0.0.1',
  PORT: '5000',
  https: false,
  debug: true, // Only on development
},

运行 Nuxt.js 服务器

现在您已经设置好了所有内容,您需要运行服务器并开始查看发生了什么。Nuxt.js附带了一些现成的预编程npm脚本。通过打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下操作,可以运行以下命令之一:

  • npm run dev-以开发模式运行服务器
  • npm run build-使用网页包构建文件,并缩小 CSS 和 JS 以用于生产

  • npm run generate-为每条路线生成静态 HTML 页面

  • npm start–在运行 build 命令后,在生产环境中启动服务器

创建 TodoList 组件

对于 TodoList 应用,我们需要一个组件来获取任务并删除任务。

单文件组件

在这里,我们将创建单文件组件的<script>部分:

  1. client/components文件夹中,创建一个名为TodoList.vue的文件并打开它。
  2. 然后,我们将导出一个defaultJavaScript 对象,其中name属性定义为TodoList,然后将beforeMount生命周期挂钩定义为异步函数。将computedmethods属性定义为空 JavaScript 对象。然后,创建一个data属性,该属性定义为返回 JavaScript 对象的单例函数。在data属性中,创建一个taskList属性作为空数组:
export default {
  name: 'TodoList',
  data: () => ({
    taskList: [],
  }),
  computed: {},
  async beforeMount() {},
  methods: {},
};
  1. computed属性中,创建一个名为taskObject的新属性。此computed属性将返回Object.fromEntries(new Map(this.taskList))的结果:
taskObject() {
      return Object.fromEntries(new Map(this.taskList));
    },
  1. methods属性中,创建一个名为getTask的新方法–它将是一个异步函数。此方法将从服务器获取任务,然后使用响应定义taskList属性:
async getTasks() {
      try {
        const { tasks } = await 
           this.$axios.$get('http://localhost:5000');
        this.taskList = tasks;
      } catch (err) {
        console.error(err);
      }
    },
  1. 然后,创建一个deleteTask方法。此方法将是一个异步函数,并将接收一个id作为参数。使用此参数,将执行 API 执行删除任务,然后执行getTask方法:
async deleteTask(i) {
      try {
        const { status } = await 
           this.$axios.$delete(`http://localhost:5000/${i}`);
        if (status) {
          await this.getTasks();
        }
      } catch (err) {
        console.error(err);
      }
    },
  1. 最后,在beforeMount生命周期钩子中,我们将执行getTask方法:
async beforeMount() {
    await this.getTasks();
  },

单文件组件

是时候创建单文件组件的<template>部分了:

  1. client/components文件夹中,打开TodoList.vue文件。
  2. <template>部分,创建一个divHTML 元素,并添加值为boxclass属性:
<div class="box"></div>
  1. 作为div.boxHTML 元素的子元素,创建divHTML 元素,其中class属性定义为content,子元素定义为olHTML 元素,属性type定义为1
<div class="content">
  <ol type="1"></ol>
</div>
  1. 作为olHTML 元素的子元素,创建一个liHTML 元素,将v-for指令定义为(task, i) in taskObject,将key属性定义为变量i
<li
  v-for="(task, i) in taskObject"
  :key="i">
</li>
  1. 最后,作为olHTML 元素的子元素,添加{{ task }}作为内部文本,作为文本的同级元素,创建一个buttonHTML 元素,class属性定义为delete is-small,事件监听器定义为deleteTask方法,将i变量作为参数传递:
{{ task }}
<button
  class="delete is-small"
  @click="deleteTask(i)"
/>

创建 Todo 表单组件

要将任务发送到服务器,我们需要一个表单。这意味着我们需要制作一个表单组件来处理这个问题。

单文件组件

在这里,我们将创建单文件组件的<script>部分:

  1. client/components文件夹中,创建一个名为TodoForm.vue的文件并打开它。
  2. 然后,我们将导出一个defaultJavaScript 对象,其中name属性定义为TodoForm,然后将methods属性定义为空 JavaScript 对象。然后,创建一个data属性,该属性定义为返回 JavaScript 对象的单例函数。在data属性中,创建一个task属性作为空数组:
export default {
  name: 'TodoForm',
  data: () => ({
    task: '',
  }),
  methods: {},
};
  1. methods属性中,创建一个名为save的方法,该方法将是一个异步函数。此方法将task发送给 API,如果 API 接收到Ok Status,则会发出一个具有tasktask属性的'new-task'事件:
async save() {
      try {
        const { status } = await 
          this.$axios.$post('http://localhost:5000/', {
          task: this.task,
        });
        if (status) {
          this.$emit('new-task', this.task);
          this.task = '';
        }
      } catch (err) {
        console.error(err);
      }
    },

单文件组件

是时候创建单文件组件的<template>部分了:

  1. client/components文件夹中,打开TodoForm.vue文件。

  2. <template>部分,创建一个divHTML 元素,并添加值为boxclass属性:

<div class="box"></div>
  1. div.boxHTML 元素内部,创建一个divHTML 元素,其class属性定义为field has-addons
<div class="field has-addons"></div>
  1. 然后,在div.field.has-addonsHTML 元素内部,创建一个子divHTML 元素,将class属性定义为control is-expanded,并添加一个子输入 HTML 元素,将v-model指令定义为task属性。然后,将class属性定义为inputtype属性定义为textplaceholder属性定义为ToDo Task。最后,在@keypress.enter事件监听器中定义save方法:
<div class="control is-expanded">
  <input
    v-model="task"
    class="input"
    type="text"
    placeholder="ToDo Task"
    @keypress.enter="save"
  >
</div>
  1. 最后,作为div.control.is-expandedHTML 元素的同级,创建一个divHTML 元素,将class属性定义为control,并添加一个子aHTML 元素,将class属性定义为button is-info,在@click事件侦听器上,将其定义为save方法。作为aHTML 元素的内部文本,添加Save Task文本:
<div class="control">
  <a
    class="button is-info"
    @click="save"
  >
    Save Task
  </a>
</div>

创建布局

现在我们需要创建一个新的布局,将应用包装为一个简单的高阶组件。在client/layouts文件夹中,打开名为default.vue的文件,删除文件的<style>部分,并将<template>部分更改为以下内容:

<template>   
    <nuxt />
</template>

创建页面

现在,我们将创建应用的主页,用户将能够在其中查看他们的TodoList并添加一个新的TodoItem

单文件组件

在这里,我们将创建单文件组件的<script>部分:

  1. 打开client/pages文件夹中的index.vue文件。
  2. 导入我们创建的todo-formtodo-list组件,然后我们将导出一个defaultJavaScript 对象,带有components属性和导入的组件:
<script>
import TodoForm from '../components/TodoForm.vue';
import TodoList from '../components/TodoList.vue';

export default {
  components: { TodoForm, TodoList },
};
</script>

单文件组件

是时候创建单文件组件的<template>部分了:

  1. client/pages文件夹中,打开index.vue文件。
  2. <template>部分,创建一个divHTML 元素,作为子元素添加一个sectionHTML 元素,class属性定义为hero is-primary。然后,作为sectionHTML 元素的子元素,创建一个divHTML 元素,class属性定义为hero-body。作为div.hero-bodyHTML 元素的子元素,创建一个class属性定义为containerdivHTML 元素,并作为子元素添加一个class属性定义为titleh1HTML 元素,内部文本为Todo App
<section class="hero is-primary">
  <div class="hero-body">
    <div class="container">
      <h1 class="title">
        Todo App
      </h1>
    </div>
  </div>
</section>
  1. 作为section.hero.is-primaryHTML 元素的同级,创建一个sectionHTML 元素,其中class属性定义为section,而style属性定义为padding: 1rem。将一个class属性定义为containerdivHTML 元素作为子元素添加到一个ref属性定义为list的子todo-list组件中:
<section
  class="section"
  style="padding: 1rem"
>
  <div class="container">
    <todo-list
      ref="list"
    />
  </div>
</section>
  1. 最后,作为section.sectionHTML 元素的同级,创建一个sectionHTML 元素,其中class属性定义为section,而style属性定义为padding: 1rem。添加一个子divHTML 元素,该元素的class属性定义为container,子todo-form组件的@new-task事件监听器定义为$refs.list.getTasks()
<section
  class="section"
  style="padding: 1rem"
>
  <div class="container">
    <todo-form
      @new-task="$refs.list.getTasks()"
    />
  </div>
</section>

它是如何工作的。。。

此配方显示了通过 Python 的本地 API 服务器与通过Nuxt.js提供服务的 SSR 平台之间的集成。

当您首先启动 Python 服务器时,您正在打开端口以作为被动客户机从客户机接收数据,只是在等待启动代码时发生一些事情。通过同样的过程,Nuxt.jsSSR 可以在幕后做很多事情,但当它完成时,它就会闲置,等待用户的操作。

当用户与前端交互时,应用可以向服务器发送一些请求,这些请求将与数据一起返回给用户,并显示在屏幕上。

另见

您可以在上了解有关 Flask 和 Python 内部 HTTP 项目的更多信息 https://palletsprojects.com/p/flask/.

如果您想了解更多关于Nuxt.js的信息,您可以阅读上的文档 https://nuxtjs.org/guide/.

如果您想了解更多有关 Axios 的Nuxt.js实现以及如何配置和使用插件的信息,您可以阅读上的文档 https://axios.nuxtjs.org/options.

如果你想了解更多关于 Bulma 的信息,这个配方中使用的 CSS 框架,你可以在找到更多信息 https://bulma.io.

Vue 应用的注意事项

安全性一直是每个人都担心的问题,这对技术来说也没有什么不同。你需要时刻保持警觉。在本节中,我们将介绍如何使用一些技术和简单的解决方案来防止攻击。

短绒

使用 ESLint 时,请确保已启用 Vue 插件,并且遵循强烈建议的规则。这些规则将帮助您进行开发,检查一些常见的错误,这些错误可能会打开攻击的大门,例如[T0]指令。

Vue-CLI项目中,选择过梁选项后,将与项目文件一起创建一个名为.eslintrc.js的文件。在此文件中,将预先确定一组基本规则。以下是ESLint + AirBnb项目的一套良好实践规则示例:

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    'plugin:vue/essential',
    'plugin:vue/recommended',
    'plugin:vue/strongly-recommended',
    '@vue/airbnb',
  ],
parserOptions: {
    parser: 'babel-eslint',
  },
rules: {
 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
 }, 
};

现在,如果您有任何违反 lint 规则的代码,它将不会在开发或构建时被解析。

JavaScript

JavaScript 有一些漏洞,可以通过遵循一些简单的检查表和简单的实现来防止。这些实现可以在客户机-服务器通信或 DOM 操作中实现,但您始终需要小心不要忘记它们。

以下是一些使用 JavaScript 的提示:

  • 尽可能使用经过身份验证和加密的 API。记住 JWT 本身并不是加密的;您需要添加加密层(JWE)来拥有整个 JSON。
  • 如果要存储 API 令牌,请始终使用SessionStorage
  • 在将 HTML 输入发送到服务器之前,请始终清理用户输入的 HTML。
  • 在将 HTML 呈现到 DOM 之前,始终对其进行清理。
  • 始终避开用户的任何RegeExp;它将被执行,以防止任何 CPU 线程攻击。
  • 始终捕获错误,不向用户显示任何堆栈跟踪,以防止任何代码操纵。

以下是一些使用 JavaScript 时不应做的提示:

  • 切勿使用eval();它使代码运行缓慢,并为恶意代码在代码内部执行打开了一扇门。
  • 在未对数据进行任何清理或转义的情况下,切勿呈现用户的任何输入。
  • 不要在没有任何清理的情况下在 DOM 上呈现任何 HTML。
  • 切勿在LocalStorage上存储 API 令牌。
  • 切勿将敏感数据存储在 JWT 对象中。

视图(&V)

在开发 Vue 应用时,您需要检查一些基本规则,这些规则有助于开发,不会为应用的外部操作打开任何大门。

以下是使用 Vue 的一些提示:

  • 始终将类型验证添加到您的道具中,如果可能,还应进行验证程序检查。
  • 避免组件的全球注册;使用本地组件。
  • 如果可能,请始终使用延迟加载的组件。
  • 使用$refs代替直接 DOM 操作。

以下是有关使用 Vue 时不应执行的操作的一些提示:

  • 切勿在窗口或任何全局范围中存储[T0]、[T1]、[T2]或任何应用变量。
  • 切勿修改 Vue 原型;如果需要向原型添加新变量,请创建新的 Vue 插件。
  • 不建议在组件之间使用直接连接,因为这样会使组件绑定到父级或子级。

另见

您可以在[T0]的 OWASP CheatCheat 上找到更多关于 XSS(跨站点脚本)的信息 https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md 和关于的 HTML XSShttps://html5sec.org/.

有关eslint-vue-plugin的更多信息,请访问https://eslint.vuejs.org/.

有关 Node.js 安全最佳实践的更多信息,请访问https://github.com/i0natan/nodebestpractices#6-安全最佳做法。

有关 Vue 应用的注意事项的更多信息,请访问https://quasar.dev/security/dos-and-donts.****