Stories

Detail Return Return

用現有bootstrap的模板,改造成nuxt3項目 - Stories Detail

為了響應快速開發企業網站,並且能夠適配移動端,完整的使用tailwind css寫一套還挺複雜。

雖然有很多的UI框架,這些框架開發管理系統還可以,有着統一的UI風格,企業網站主要面向C端用户,有着不同設計風格需求,那麼之前的bootstrap佈局的頁面還是很不錯的選擇。

比如就可以在模板王中下載一套項目代碼,通過將內容和文字做一些修改,即可給客户使用。

接下來是改造的過程:

改造最初通過詢問AI,給出了2個方案;

  • 第一,使用bootstrap-vue-next,然後配合tailwind css進行改造【使用的此方法,改動量很大,放棄】;
  • 第二,將js和css文件遷移的public目錄下,然後在項目中加載,這樣只需要將html文件修改為.vue的文件類型,然後修改很少的鏈接跳轉方式即可。

本文中採用第二種方案。

<font style="background-color:#D9EAFC;">遷移過程最痛苦的2件事,在vue中js的加載時機 和 遷移靜態資源public/assets。</font>

下載代碼

本次改造的項目代碼,原模板下載https://www.mobanwang.com/mb/demo/22705/;

也可以下載其他網絡上的優秀的企業站代碼。

├── about.html
├── assets
│   ├── css
│   │   ├── bootstrap.min.css
│   │   ├── em-breadcrumb.css
│   │   ├── plugin_theme_css.css
│   │   └── responsive.css
│   ├── fonts
│   │   ├── Flaticon.woff
│   │   ├── Flaticon.woff2
│   │   ├── Sofia Pro Bold.ttf
│   │   ├── aprova0698.eot
│   │   ├── aprova0698.svg
│   │   ├── aprova0698.ttf
│   │   ├── aprova0698.woff
│   │   ├── fontawesome-webfont3295.ttf
│   │   ├── fontawesome-webfont3295.woff
│   │   ├── fontawesome-webfont3295.woff2
│   │   ├── icofont.eot
│   │   ├── icofont.svg
│   │   ├── icofont.ttf
│   │   ├── icofont.woff
│   │   ├── icofont.woff2
│   │   ├── themify.ttf
│   │   └── themify.woff
│   ├── images
│   │   ├── about-img-1.jpg
│   │   ├── b1.jpg
│   │   ├── b2.jpg
│   │   ├── b3.jpg
│   │   ├── b4.jpg
│   │   ├── b5.jpg
│   │   ├── b6.jpg
│   │   ├── b7.jpg
│   │   ├── b8.jpg
│   │   ├── blog-sidebar1.jpg
│   │   ├── blog-sidebar2.jpg
│   │   ├── blog-sidebar3.jpg
│   │   ├── br1.jpg
│   │   ├── br2.jpg
│   │   ├── br3.jpg
│   │   ├── br4.jpg
│   │   ├── br5.jpg
│   │   ├── contact-bg.jpg
│   │   ├── faq-img.png
│   │   ├── favicon.png
│   │   ├── fottor-bg.jpg
│   │   ├── logo1.png
│   │   ├── logo2.png
│   │   ├── service-bg-img.jpg
│   │   ├── service-img.png
│   │   ├── single-blog.jpg
│   │   ├── single-service.jpg
│   │   ├── skill-img.jpg
│   │   ├── slide-03.jpg
│   │   ├── slider1.jpg
│   │   ├── slider2.jpg
│   │   ├── tab-img.jpg
│   │   ├── tab-img2.jpg
│   │   ├── tab-img3.jpg
│   │   ├── team-bg.jpg
│   │   ├── team1.jpg
│   │   ├── team1.png
│   │   ├── team2.jpg
│   │   ├── team2.png
│   │   ├── team3.jpg
│   │   ├── team3.png
│   │   ├── team4.png
│   │   ├── test1.png
│   │   ├── test2.png
│   │   ├── test3.png
│   │   └── test4.png
│   ├── js
│   │   ├── BeerSlider.js
│   │   ├── ajax-mail.js
│   │   ├── bootstrap.min.js
│   │   ├── bootstrap.min.js.map
│   │   ├── customizer.js
│   │   ├── imagesloaded.pkgd.min.js
│   │   ├── isotope.pkgd.min.js
│   │   ├── jquery.appear.js
│   │   ├── jquery.knob.js
│   │   ├── jquery.meanmenu.js
│   │   ├── jquery.nivo.slider.pack.js
│   │   ├── jquery.waitforimages.js
│   │   ├── map.js
│   │   ├── modernizr.custom.79639.js
│   │   ├── owl.carousel.min.js
│   │   ├── slick.min.js
│   │   ├── swiper-bundle.min.js.map
│   │   ├── theme-pluginjs.js
│   │   ├── theme.js
│   │   └── vendor
│   │       ├── jquery-3.5.1.min.js
│   │       └── modernizr-2.8.3.min.js
│   └── webfonts
│       ├── fa-brands-400.eot
│       ├── fa-brands-400.svg
│       ├── fa-brands-400.ttf
│       ├── fa-brands-400.woff
│       ├── fa-brands-400.woff2
│       ├── fa-regular-400.eot
│       ├── fa-regular-400.svg
│       ├── fa-regular-400.ttf
│       ├── fa-regular-400.woff
│       ├── fa-regular-400.woff2
│       ├── fa-solid-900.eot
│       ├── fa-solid-900.svg
│       ├── fa-solid-900.ttf
│       ├── fa-solid-900.woff
│       └── fa-solid-900.woff2
├── blog-left-sidebar.html
├── blog-right-sidebar.html
├── blog.html
├── contact.html
├── faq.html
├── home-video.html
├── index.html
├── landing-page.html
├── portfolio-3column.html
├── portfolio-4column.html
├── portfolio.html
├── pricing-table.html
├── service.html
├── single-blog.html
├── single-service.html
├── style.css
├── team.html
├── testimonial.html
└── venobox
    ├── close.gif
    ├── next.gif
    ├── preload-circle.png
    ├── preload-dots.png
    ├── preload-ios.png
    ├── preload-quads.png
    ├── preload.png
    ├── prev.gif
    ├── venobox.css
    ├── venobox.js
    └── venobox.min.js

9 directories, 133 files

遷移靜態資源

分為3中情況

  • assets下的圖片資源,統一存放到public/assets下,後邊調整代碼來獲取該路徑的資源
  • 將assets下的js和css和font資源,放到public/assets下的js和css和font

將venobox和style.css文件也遷移到public/assets下,style.css可以放到public/assets/css的目錄下,<font style="color:#DF2A3F;">注意這裏需要將</font><font style="color:#DF2A3F;">style.css</font><font style="color:#DF2A3F;">的圖片引用,修改為相對引用地址。</font>

  • 將html結尾的文件,複製body部分的代碼到vue文件的template中。

關於js腳本加載的問題

在bootstrap中,每個頁面為獨立html頁面,打開都會加載js腳本,並且加載腳本的時間在dom結構渲染完成後進行加載。

那麼在改寫的vue中,就需要onMounted的生命週期中加載。

在改造過程中嘗試了幾種方案:

  • 寫到plugin中,通過nuxtApp.hook('app:mounted', async () => {})的生命週期時機進行加載,這種方法對index.vue頁面生效,但是隻加載了一次,對其他頁面會失效。
  • 第二種還是想放到插件中,讓每個頁面的路由之後,加載js
if (process.client) {
  const router = useRouter();

  // 監聽路由變化,模擬每個頁面的 mounted
  router.afterEach(async (to, from) => {
    console.log('頁面 mounted 模擬:', to.path);
    // 在這裏執行你的邏輯
    // 加載腳本、埋點、初始化第三方庫等
    // const scripts = [
    //   '/js/vendor/modernizr-2.8.3.min.js',
    //   '/js/vendor/jquery-3.5.1.min.js',
    //   '/js/bootstrap.min.js',
    //   '/js/isotope.pkgd.min.js',
    //   '/js/owl.carousel.min.js',
    //   '/js/jquery.nivo.slider.pack.js',
    //   '/js/slick.min.js',
    //   '/venobox/venobox.min.js',
    //   '/js/imagesloaded.pkgd.min.js',
    //   '/js/jquery.appear.js',
    //   '/js/jquery.knob.js',
    //   '/js/BeerSlider.js',
    //   '/js/theme-pluginjs.js',
    //   '/js/jquery.meanmenu.js',
    //   '/js/ajax-mail.js',
    //   '/js/theme.js',
    // ];

    // for (const src of scripts) {
    //   try {
    //     await loadScript(src);
    //     console.log('腳本加載成功:', src);

    //   } catch (err) {
    //     console.error(err);
    //   }
    // }
  });
}

這種方案不能使用<nuxt-link>標籤,使用此標籤跳轉的頁面,還是無法正常加載和顯示頁面。使用<a>時可以生效,但是會刷新頁面。

也放棄了這個方案

  • 使用composables下寫一個公用的加載js的函數方法,在每個頁面的onMounted週期中調用一下,這算是最好的解決辦法。
// 緩存已加載的腳本
const loadedScripts = new Set<string>();

const loadScript = (src: string) => {
  return new Promise((resolve, reject) => {
    if (loadedScripts.has(src)) {
      resolve(true);
      return;
    }

    const script = document.createElement('script');
    script.src = src;
    script.defer = true;

    script.onload = () => {
      loadedScripts.add(src);
      resolve(true);
    };

    script.onerror = () => {
      reject(new Error(`Failed to load script: ${src}`));
    };

    document.body.appendChild(script);
  });
};
export const loadScriptClient = async () => {
  const script1 = document.createElement('script');
  script1.src = '/js/vendor/modernizr-2.8.3.min.js';
  document.body.appendChild(script1);

  const script2 = document.createElement('script');
  script2.src = '/js/vendor/jquery-3.5.1.min.js';
  document.body.appendChild(script2);

  const script3 = document.createElement('script');
  script3.src = '/js/bootstrap.min.js';
  document.body.appendChild(script3);

  const script4 = document.createElement('script');
  script4.src = '/js/isotope.pkgd.min.js';
  document.body.appendChild(script4);

  const script5 = document.createElement('script');
  script5.src = '/js/owl.carousel.min.js';
  document.body.appendChild(script5);

  const script6 = document.createElement('script');
  script6.src = '/js/jquery.nivo.slider.pack.js';
  document.body.appendChild(script6);

  const script7 = document.createElement('script');
  script7.src = '/js/slick.min.js';
  document.body.appendChild(script7);

  const script18 = document.createElement('script');
  script18.src = '/venobox/venobox.min.js';
  document.body.appendChild(script18);

  const script8 = document.createElement('script');
  script8.src = '/js/imagesloaded.pkgd.min.js';
  document.body.appendChild(script8);

  const script9 = document.createElement('script');
  script9.src = '/js/jquery.appear.js';
  document.body.appendChild(script9);

  const script10 = document.createElement('script');
  script10.src = '/js/jquery.knob.js';
  document.body.appendChild(script10);

  const script11 = document.createElement('script');
  script11.src = '/js/BeerSlider.js';
  document.body.appendChild(script11);

  const script12 = document.createElement('script');
  script12.src = '/js/theme-pluginjs.js';
  document.body.appendChild(script12);

  const script13 = document.createElement('script');
  script13.src = '/js/jquery.meanmenu.js';
  document.body.appendChild(script13);

  const script14 = document.createElement('script');
  script14.src = '/js/ajax-mail.js';
  document.body.appendChild(script14);

  const script15 = document.createElement('script');
  script15.src = '/js/theme.js';
  document.body.appendChild(script15);
  
};

在vue的頁面中進行調用

// 初始化腳本
onMounted(() => {
  loadScriptClient();
})

關於圖片資源引用的問題

public/css內部應用的圖片路徑地址

public/assets/css內部應用的圖片路徑地址,是public/assets/images中的資源;

.consit_service_area2 {
    background-image: url("../images/port-bg-img.jpg");
    background-repeat: no-repeat;
    background-size: cover;
    padding: 120px 0px 110px;
}

vue文件的template的圖片

template模版內的圖片引入:

  • 使用"/assets/images/logo1.png"的也是public/assets/images目錄的資源。
  • 如果代碼"assets/images/logo1.png"則是使用了assets/images目錄資源
<template>
  <div class="mobile_menu_logo text-center">
    <a href="index.html" title="consit">
      <img src="/assets/images/logo1.png" alt="consit" />
    </a>
  </div>
</template>

vue文件的template的style中使用圖片

在template中的style添加背景圖片,這裏如果正常使用/assets/images/logo1.png,就會引用到/assets/images的資源,而不是public/assets/image的資源。

下面代碼引用了 assets/image的資源

這裏不管有沒有 / 路徑,都是使用的 assets/image

<template>
  <div
      class="swiper-slide d1 t1 m1 witr_swiper_height"
      style="background-image:    url(/assets/images/slider1.jpg)"
    >
    1111
  </div>
</template>

可以結合script,使用public的資源;

主要思路是通過動態導入圖片資源,然後在綁定到style中

<template>
  <div
      class="swiper-slide d1 t1 m1 witr_swiper_height"
      :style="{ backgroundImage: `url(${slider1})` }"
    >
    1111
  </div>
</template>
<script setup>
  // vue script內引入assets圖片的方法
  import slider1 from '/assets/images/slider1.jpg';
</script>

遷移完成後代碼結構

./
├── README.md
├── app.vue
├── components
│   ├── navFooter.vue
│   └── navHeader.vue
├── composables
│   └── index.ts
│── nuxt.config.ts
├── package.json
├── pages
│   ├── about.vue
│   ├── blog.vue
│   ├── blogLeft.vue
│   ├── blogRight.vue
│   ├── contact.vue
│   ├── faq.vue
│   ├── homeVideo.vue
│   ├── index.vue
│   ├── landingPage.vue
│   ├── portfolio.vue
│   ├── portfolio3column.vue
│   ├── portfolio4column.vue
│   ├── pricingTable.vue
│   ├── service.vue
│   ├── serviceSingle.vue
│   ├── singleBlog.vue
│   ├── team.vue
│   └── testimonial.vue
├── plugins
│   └── load-script.client.ts
├── pnpm-lock.yaml
├── public
│   ├── assets
│   │   └── images
│   │       ├── about-img-1.jpg
│   │       ├── b1.jpg
│   │       ├── b2.jpg
│   │       ├── b3.jpg
│   │       ├── b4.jpg
│   │       ├── ...
│   │       ├── css
│   │       │   ├── bootstrap.min.css
│   │       │   ├── em-breadcrumb.css
│   │       │   ├── plugin_theme_css.css
│   │       │   ├── responsive.css
│   │       │   └── style.css
│   │       ├── fonts
│   │       │   ├── Flaticon.woff
│   │       │   ├── ...
│   │       ├── js
│   │       │   ├── BeerSlider.js
│   │       │   ├── ajax-mail.js
│   │       │   ├── ...
│   │       ├── venobox
│   │       │   ├── close.gif
│   │       │   ├── next.gif
│   │       │   ├── preload-circle.png
│   │       │   ├── preload-dots.png
│   │       │   ├── preload-ios.png
│   │       │   ├── preload-quads.png
│   │       │   ├── preload.png
│   │       │   ├── prev.gif
│   │       │   ├── venobox.css
│   │       │   ├── venobox.js
│   │       │   └── venobox.min.js
│   │       └── webfonts
│   │           ├── fa-brands-400.eot
│   │           ├── fa-brands-400.svg
│   │           ├── ... 
│   ├── favicon.ico
│   ├── favicon.png
│   ├── robots.txt
└── tsconfig.json

29 directories, 146 files

以上是將bootstrap項目轉為nuxt項目的代碼結構。

關於接口調用和打包發佈上線的操作

詳細的操作和項目代碼倉庫,放置到個人網站中,如有需要請查看;

  • 文章地址:https://shenshuai89.github.io/pages/3926f2/
  • 線上預覽地址:https://www.shenshuai.site/nuxtapp202504114/

Add a new Comments

Some HTML is okay.