草庐IT

vite+vue3项目最佳起始点(保姆级)

贺haha 2023-03-28 原文

一 、通过云开发平台快速创建初始化应用

1.创建相关应用模版请参考链接:去中心化的前端构建工具 — Vite

2.完成创建后就可以在github中查看到新增的Vite仓库

二 、 本地编写 Vite后台项目最佳起始点

1.将应用模版克隆到本地

  • 首先假定你已经安装了Git、node,没有安装请移步node官网进行安装。克隆项目:
git clone + 项目地址
  • 进入项目文件
cd Vite
  • 切换到feature/1.0.0 分支上
git checkout feature/1.0.0
  • 安装依赖包
npm install
  • 启动服务
 npm run dev

这里打开浏览器3000端口,并出现默认页面。

2.路由

  • 安装 vue-router 4.x
npm i vue-router@next -S

  • 路由配置 router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/', component: () => import('views/home.vue') }
  ]
});

export default router
  • 引入 main.js
import router from "@/router";
createApp(App).use(router).mount("#app");

3.状态管理

  • 安装 vuex 4.x
npm i vuex@next -S

  • Store配置 store/index.js
import {createStore} from 'vuex';

export default createStore({
  state: {
    couter: 0
  }
});
  • 引入 main.js
import store from "@/store";
createApp(App).use(store).mount("#app");

4.样式组织

  • 安装 sass
npm i sass -D

styles 目录保存各种样式

index.scss 作为出口组织这些样式,同时编写一些全局样式

最后在main.js导入

5.UI库

  • 安装
npm i element3 -S
  • 完整引入 main.js
import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

createApp(App).use(element3)
  • 按需引入 main.js
import "element3/lib/theme-chalk/button.css";
import { ElButton } from "element3"
createApp(App).use(ElButton)

抽取成插件会更好 plugins/element3.js

// 完整引入
import element3 from "element3";
import "element3/lib/theme-chalk/index.css";

// 按需引入
// import { ElButton } from "element3";
// import "element3/lib/theme-chalk/button.css";

export default function (app) {
  // 完整引入
  app.use(element3)

  // 按需引入
  // app.use(ElButton);
}
  • 测试
<el-button>my button</el-button>

6.基础布局

我们应用需要一个基本布局页,类似下图,将来每个页面以布局页为父页面即可:

  • 布局页面 layout/index.vue
<template>
  <div class="app-wrapper">
    <!-- 侧边栏 -->
    <div class="sidebar-container"></div>
    <!-- 内容容器 -->
    <div class="main-container">
      <!-- 顶部导航栏 -->
      <navbar />
      <!-- 内容区 -->
      <app-main />
    </div>
  </div>
</template>

<script setup>
import AppMain from "./components/AppMain.vue";
import Navbar from "./components/Navbar.vue";
</script>

<style lang="scss" scoped>
@import "../styles/mixin.scss";

.app-wrapper {
  @include clearfix;
  position: relative;
  height: 100%;
  width: 100%;
}
</style>
  • 路由配置 router/index.js
{
  path: "/",
	component: Layout,
  children: [
    {
      path: "",
      component: () => import('views/home.vue'),
      name: "Home",
      meta: { title: "首页", icon: "el-icon-s-home" },
    },
  ],
},

7.动态导航

  • 侧边导航

根据路由表动态生成侧边导航菜单。

首先创建侧边栏组件,递归输出routes中的配置为多级菜单,layout/Sidebar/index.vue

<template>
  <el-scrollbar wrap-class="scrollbar-wrapper">
    <el-menu
      :default-active="activeMenu"
      :background-color="variables.menuBg"
      :text-color="variables.menuText"
      :unique-opened="false"
      :active-text-color="variables.menuActiveText"
      mode="vertical"
    >
      <sidebar-item
        v-for="route in routes"
        :key="route.path"
        :item="route"
        :base-path="route.path"
      />
    </el-menu>
  </el-scrollbar>
</template>

<script setup>
import SidebarItem from "./SidebarItem.vue";
import { computed } from "vue";
import { useRoute } from "vue-router";
import { routes } from "@/router";
import variables from "styles/variables.module.scss";

const activeMenu = computed(() => {
  const route = useRoute();
  const { meta, path } = route;
  if (meta.activeMenu) {
    return meta.activeMenu;
  }
  return path;
});
</script>
  • 添加相关样式:

○ styles/variables.module.scss

○ styles/sidebar.scss

○ styles/index.scss中引入

创建SidebarItem.vue组件,解析当前路由是导航链接还是父菜单:

8.面包屑

通过路由匹配数组可以动态生成面包屑。

面包屑组件,layouts/components/Breadcrumb.vue

<template>
  <el-breadcrumb class="app-breadcrumb" separator="/">
      <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
        <span
          v-if="item.redirect === 'noRedirect' || index == levelList.length - 1"
          class="no-redirect"
          >{{ item.meta.title }}</span>
        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
      </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup>
import { compile } from "path-to-regexp";
import { reactive, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";

const levelList = ref(null);
const router = useRouter();
const route = useRoute();

const getBreadcrumb = () => {
  let matched = route.matched.filter((item) => item.meta && item.meta.title);

  const first = matched[0];
  if (first.path !== "/") {
    matched = [{ path: "/home", meta: { title: "首页" } }].concat(matched);
  }

  levelList.value = matched.filter(
    (item) => item.meta && item.meta.title && item.meta.breadcrumb !== false
  );
}

const pathCompile = (path) => {  
  var toPath = compile(path);
  return toPath(route.params);
}

const handleLink = (item) => {
  const { redirect, path } = item;
  if (redirect) {
    router.push(redirect);
    return;
  }
  router.push(pathCompile(path));
}

getBreadcrumb();
watch(route, getBreadcrumb)

</script>

<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
  display: inline-block;
  font-size: 14px;
  line-height: 50px;
  margin-left: 8px;

  .no-redirect {
    color: #97a8be;
    cursor: text;
  }
}
</style>

9.数据封装

统一封装数据请求服务,有利于解决一下问题:

  • 统一配置请求
  • 请求、响应统一处理

准备工作:

  • 安装axios:
npm i axios -S

添加配置文件:.env.development

VITE_BASE_API=/api

请求封装 utils/request.js

import axios from "axios";
import { Message, Msgbox } from "element3";

// 创建axios实例
const service = axios.create({
  // 在请求地址前面加上baseURL
  baseURL: import.meta.env.VITE_BASE_API,
  // 当发送跨域请求时携带cookie
  // withCredentials: true,
  timeout: 5000,
});

// 请求拦截
service.interceptors.request.use(
  (config) => {
    // 模拟指定请求令牌
    config.headers["X-Token"] = "my token";
    return config;
  },
  (error) => {
    // 请求错误的统一处理
    console.log(error); // for debug
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  /**
   * 通过判断状态码统一处理响应,根据情况修改
   * 同时也可以通过HTTP状态码判断请求结果
   */
  (response) => {
    const res = response.data;

    // 如果状态码不是20000则认为有错误
    if (res.code !== 20000) {
      Message.error({
        message: res.message || "Error",
        duration: 5 * 1000,
      });

      // 50008: 非法令牌; 50012: 其他客户端已登入; 50014: 令牌过期;
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
        // 重新登录
        Msgbox.confirm("您已登出, 请重新登录", "确认", {
          confirmButtonText: "重新登录",
          cancelButtonText: "取消",
          type: "warning",
        }).then(() => {
          store.dispatch("user/resetToken").then(() => {
            location.reload();
          });
        });
      }
      return Promise.reject(new Error(res.message || "Error"));
    } else {
      return res;
    }
  },
  (error) => {
    console.log("err" + error); // for debug
    Message({
      message: error.message,
      type: "error",
      duration: 5 * 1000,
    });
    return Promise.reject(error);
  }
);

export default service;

10.常见业务处理

  • 结构化数据展

使用el-table展示结构化数据,配合el-pagination做数据分页。

文件组织结构如下:list.vue展示列表,edit.vue和create.vue编辑或创建,内部复用detail.vue处理,model中负责数据业务处理。

list.vue中的数据展示

<el-table v-loading="loading" :data="list">
  <el-table-column label="ID" prop="id"></el-table-column>
  <el-table-column label="账户名" prop="name"></el-table-column>
  <el-table-column label="年龄" prop="age"></el-table-column>
</el-table>

list和loading数据的获取逻辑,可以使用compsition-api提取到userModel.js

export function useList() {
  // 列表数据
  const state = reactive({
    loading: true, // 加载状态
    list: [], // 列表数据
  });

  // 获取列表
  function getList() {
    state.loading = true;
    return request({
      url: "/getUsers",
      method: "get",
    }).then(({ data, total }) => {
      // 设置列表数据
      state.list = data;
    }).finally(() => {
      state.loading = false;
    });
  }
  
  // 首次获取数据
  getList();

  return { state, getList };
}

list.vue中使用

import { useList } from "./model/userModel";
const { state, getList } = useList();

分页处理 list.vue

<pagination
      :total="total"
      v-model:page="listQuery.page"
      v-model:limit="listQuery.limit"
      @pagination="getList"
    ></pagination>

数据也在userModel中处理

const state = reactive({
  total: 0,   // 总条数
  listQuery: {// 分页查询参数
    page: 1,  // 当前页码
    limit: 5, // 每页条数
  },
});
request({
  url: "/getUsers",
  method: "get",
  params: state.listQuery, // 在查询中加入分页参数
})

11.表单处理

用户数据新增、编辑使用el-form处理

可用一个组件detail.vue来处理,区别仅在于初始化时是否获取信息回填到表单。

<el-form ref="form" :model="model" :rules="rules">
  <el-form-item prop="name" label="用户名">
    <el-input v-model="model.name"></el-input>
  </el-form-item>
  <el-form-item prop="age" label="用户年龄">
    <el-input v-model.number="model.age"></el-input>
  </el-form-item>
  <el-form-item>
    <el-button @click="submitForm" type="primary">提交</el-button>
  </el-form-item>
</el-form>

数据处理同样可以提取到userModel中处理。

export function useItem(isEdit, id) {
  const model = ref(Object.assign({}, defaultData));

  // 初始化时,根据isEdit判定是否需要获取详情
  onMounted(() => {
    if (isEdit && id) {
      // 获取详情
      request({
        url: "/getUser",
        method: "get",
        params: { id },
      }).then(({ data }) => {
        model.value = data;
      });
    }
  });
  return { model };
}

三 、 云端一键部署上线应用

1.上传代码

git add . 
git commit -m '添加你的注释'
git push

2.在日常环境部署

一键进行应用部署。在应用详情页面点击日常环境的「部署」按钮进行一键部署,部署状态变成绿色已部署以后可以点击访问部署网站查看效果。

3.配置自定义域名在线上环境上线

  • 配置线上环境自定义域名。在功能开发验证完成后要在线上环境进行部署,在线上环境的「部署配置」-「自定义域名」中填写自己的域名。例如我们添加一个二级域名 company.workbench.fun 来绑定我们部署的前端应用。然后复制自定义域名下方的API网关地址对添加的二级域名进行CNAME配置。

  • 配置CNAME地址。复制好 API网关域名地址后,来到你自己的域名管理平台(此示例中的域名管理是阿里云的域名管理控制台,请去自己的域名控制台操作)。添加记录的「记录类型」选择「CNAME」,在「主机记录」中输入你要创建的二级域名,这里我们输入「company」,在「记录值」中粘贴我们之前复制的 API网关域名地址,「TTL」保留默认值或者设置一个你认为合适的值即可。

  • 在线上环境部署上线。回到云开发平台的应用详情页面,按照部署的操作,点击线上环境的「部署按钮」,部署完成以后就在你自定义的域名进行了上线。CNAME 生效之后,我们输入 company.workbench.fun(示例网址) 可以打开部署的页面。至此,如何部署一个应用到线上环境,如何绑定自己的域名来访问一个线上的应用就完成了,赶紧部署自己的应用到线上环境,用自己的域名玩起来吧 ;)

4.项目预览效果

一键创建Vite应用模版链接 :https://workbench.aliyun.com/application/front/create?fromConfig=27&fromRepo=sol_github_27

参考文献:https://juejin.cn/post/6926822933721513998#heading-22

有关vite+vue3项目最佳起始点(保姆级)的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  5. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  6. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. hadoop安装之保姆级教程(二)之YARN的配置 - 2

    1.1.1 YARN的介绍 为克服Hadoop1.0中HDFS和MapReduce存在的各种问题⽽提出的,针对Hadoop1.0中的MapReduce在扩展性和多框架⽀持⽅⾯的不⾜,提出了全新的资源管理框架YARN. ApacheYARN(YetanotherResourceNegotiator的缩写)是Hadoop集群的资源管理系统,负责为计算程序提供服务器计算资源,相当于⼀个分布式的操作系统平台,⽽MapReduce等计算程序则相当于运⾏于操作系统之上的应⽤程序。 YARN被引⼊Hadoop2,最初是为了改善MapReduce的实现,但是因为具有⾜够的通⽤性,同样可以⽀持其他的分布式计算模

  9. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  10. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

随机推荐