基于Taro4打造的一款最新版微信小程序、H5的多端开发简单模板

基于Taro4、Vue3、TypeScript、Webpack5打造的一款最新版微信小程序、H5的多端开发简单模板

1

特色

  • 🛠️ Taro4, Vue 3, Webpack5, pnpm10

  • 💪 TypeScript 全新类型系统支持

  • 🍍 使用 Pinia 的状态管理

  • 🎨 Tailwindcss4 - 目前流行的原子化 CSS 框架,用于快速UI开发

  • 🔥 使用 新的 <script setup> 语法

  • 🚀 NutUI-Vue - 无需引入直接使用高质量组件,覆盖移动端主流场景

  • 🌍 API 采用模块化导入方式 根据demo.js文件设置接口,以API_xxx_method的方式命名,在请求时无需导入 直接使用useRequest()函数返回参数以解构的方式获取,拿到即为写入的接口

准备

本项目搭建采用环境如下

vscode v1.103.2
node v22.14.0
taro v4.1.5
pnpm v10.11.0

步入正题

创建项目基本结构

  1. 打开vscode编辑器终端进行操作

  2. 安装taro脚手架

    pnpm install -g @tarojs/cli
    
  3. 创建基础模版

    taro init my-taro-project
    

    根据相应提示进行如下选择
    请添加图片描述

  4. 进入创建的 my-taro-project终端目录下
    为了可以同时并且实时的预览小程序和h5,更改下config/index.ts文件中的部分内容

    outputRoot: `dist/${process.env.TARO_ENV}`
    
  5. 直接运行微信小程序自动安装依赖

    pnpm dev:weapp
    
  6. 打开微信开发者工具

    如未安装点击下面链接下载安装即可👇
    https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

    打开编译过后的微信小程序代码在:`dist》weapp`目录
    

    请添加图片描述

项目结构介绍

https://docs.taro.zone/docs/folder

接入Pinia

安装
pnpm install pinia taro-plugin-pinia
修改配置

修改config/index.ts内容

plugins: ["taro-plugin-pinia",
],
compiler: {type: "webpack5",prebundle: {enable: false, // 开启后导致pinia丢失响应式},
},
引入使用

创建以下文件夹以及文件
请添加图片描述

写入以下代码
index.ts

import { createPinia } from "pinia";
import type { App } from "vue";export const piniaStore = createPinia();
export function setupStore(app: App) {app.use(piniaStore);
}

demo.ts

import { defineStore } from 'pinia'
import { piniaStore } from '@/stores'const useCounterStore = defineStore('counter', {state: () => {return { count: 0 }},actions: {increment() {this.count++},},
})export function useCounterOutsideStore() {return useCounterStore(piniaStore)
}

使用pinia
app.ts

import { createApp } from "vue";
import { setupStore } from "./stores"; // +
import "./app.css";const App = createApp({onShow(options) {console.log("App onShow.");},
});
setupStore(App); // +export default App;

src/pages/index/index.ts

<template><view>{{ msg }}</view><view class="text-red-500" @tap="testPinia">点我测试pinia{{ count }}</view>
</template><script setup lang="ts">
import { ref, computed } from 'vue'
import './index.css'
import { useCounterOutsideStore } from '@/stores/modules/demo'const msg = ref('Hello world')
const count = computed(() => counterStore.count)
const counterStore = useCounterOutsideStore()
const testPinia = () => {counterStore.increment()console.log(counterStore.count)
}
</script>

运行结果
请添加图片描述

接入Tailwind

安装
pnpm install -D tailwindcss @tailwindcss/postcss postcss weapp-tailwindcss autoprefixer

https://github.com/sonofmagic/weapp-tailwindcss

创建写入

创建 postcss.config.js 并注册 tailwindcss

export default {plugins: {"@tailwindcss/postcss": {},autoprefixer: {},},
}

tailwind.config.ts

/** @type {import('tailwindcss').Config} */
module.exports = {// 这里给出了一份 taro 通用示例,具体要根据你自己项目的目录结构进行配置// 比如你使用 vue3 项目,你就需要把 vue 这个格式也包括进来// 不在 content glob 表达式中包括的文件,在里面编写 tailwindcss class,是不会生成对应的 css 工具类的content: ['./public/index.html', './src/**/*.{html,js,ts,jsx,tsx}'],// 其他配置项 ...corePlugins: {// 小程序不需要 preflight,因为这主要是给 h5 的,如果你要同时开发多端,你应该使用 process.env.TARO_ENV 环境变量来控制它preflight: false,},
}

package.json

"scripts": {
+ "postinstall": "weapp-tw patch"
}

添加这段脚本的用途是,每次安装包后,都会自动执行一遍 weapp-tw patch 这个脚本,给本地的 tailwindcss 打上小程序支持补丁。

config/index.js

mini: {webpackChain(chain) {chain.resolve.plugin("tsconfig-paths").use(TsconfigPathsPlugin);chain.merge({plugin: {install: {plugin: UnifiedWebpackPluginV5,args: [{// 这里可以传参数rem2rpx: true,tailwindcss: {v4: {cssEntries: [// 你 @import "weapp-tailwindcss"; 那个文件绝对路径path.resolve(__dirname, "../src/app.css"),],},},// https://github.com/sonofmagic/weapp-tailwindcss/issues/155injectAdditionalCssVarScope: true, // 解决nutui对tailwindcss的影响},],},},});}
},

src/app.css

@import "weapp-tailwindcss";

使用tailwind

src/pages/index/index.ts

<template><view>{{ msg }}</view><view class="text-red-500" @tap="testPinia">点我测试pinia{{ count }}</view><view className="text-[#acc855] text-[100px]">Hello world!</view><view class="outer"><view class="inner">嵌套样式测试</view><view class="w-[50%] h-5 bg-amber-400"></view></view>
</template><script setup lang="ts">
import { ref, computed } from 'vue'
import './index.css'
import { useCounterOutsideStore } from '@/stores/modules/demo'const msg = ref('Hello world')
const count = computed(() => counterStore.count)
const counterStore = useCounterOutsideStore()
const testPinia = () => {counterStore.increment()console.log(counterStore.count)
}
</script>

src/pages/index/index.css

.outer{.inner{color: blue;font-size: xx-large;}
}

在tailwind4版本中已经实现了 样式嵌套功能所以不用sass、less这些 也可以嵌套样式编写

运行结果

请添加图片描述

接入NutUI

安装
pnpm add @nutui/nutui-taro @nutui/icons-vue-taro @tarojs/plugin-html

@tarojs/plugin-html 使用 HTML 标签,nutui需要用到

自动按需引入nutui组件

pnpm add @nutui/auto-import-resolver unplugin-vue-components -D
写入使用

在config/index.js添加以下相应配置

import ComponentsPlugin from 'unplugin-vue-components/webpack'
import NutUIResolver from '@nutui/auto-import-resolver'config = {// 开启 HTML 插件plugins: ['@tarojs/plugin-html'],designWidth (input) {// 配置 NutUI 375 尺寸if (input?.file?.replace(/\\+/g, '/').indexOf('@nutui') > -1) {return 375}// 全局使用 Taro 默认的 750 尺寸return 750},deviceRatio: {640: 2.34 / 2,750: 1,828: 1.81 / 2,375: 2 / 1},// 小程序开发mini: {webpackChain(chain) {chain.plugin('unplugin-vue-components').use(ComponentsPlugin({resolvers: [NutUIResolver({taro: true})]}))},},// Taro-H5 开发h5: {webpackChain(chain) {chain.plugin('unplugin-vue-components').use(ComponentsPlugin({resolvers: [NutUIResolver({taro: true})]}))},}
}

src/app.ts

import { createApp } from "vue";
import { setupStore } from "./stores";
import "@nutui/nutui-taro/dist/style.css"; // +
import "./app.css";const App = createApp({onShow(options) {console.log("App onShow.");},
});
setupStore(App);export default App;

配置完成后,可以直接在模板中使用 NutUI 组件,unplugin-vue-components 插件会自动注册对应的组件,并按需引入组件样式。

使用NutUI

src/pages/index/index.ts

<template><view>{{ msg }}</view><view class="text-red-500" @tap="testPinia">点我测试pinia{{ count }}</view><view className="text-[#acc855] text-[100px]">Hello world!</view><view class="outer"><view class="inner">嵌套样式测试</view><view class="w-[50%] h-5 bg-amber-400"></view></view><nut-button type="info">测试nutui</nut-button>
</template><script setup lang="ts">
import { ref, computed } from 'vue'
import './index.css'
import { useCounterOutsideStore } from '@/stores/modules/demo'const msg = ref('Hello world')
const count = computed(() => counterStore.count)
const counterStore = useCounterOutsideStore()
const testPinia = () => {counterStore.increment()console.log(counterStore.count)
}
</script>

运行结果

请添加图片描述

接入自定义Tabbar

创建所需的目录结构

请添加图片描述

写入代码

src/stores/modules/system.ts

import { defineStore } from "pinia";
import { piniaStore } from "@/stores";type SystemState = {tabbar: {active: string;};
};const useSystemStore = defineStore("system", {state: (): SystemState => {return { tabbar: { active: "home" } };},actions: {setActiveTab(tab: string) {if (tab === this.tabbar.active) return;this.tabbar.active = tab;},},
});export function useSystemOutsideStore() {return useSystemStore(piniaStore);
}

将nut-tabbar的切换状态存入store为了解决页面重复渲染tabbar引起的问题 https://github.com/jd-opensource/nutui/issues/2368

router/index.ts

import Taro from "@tarojs/taro";type Params = Record<string, string | number | boolean | undefined | null>;interface Router {push(url: string, params?: Params): void;replace(url: string, params?: Params): void;switchTab(url: string, params?: Params): void;reLaunch(url: string, params?: Params): void;
}function buildQuery(params: Params): string {return Object.keys(params).map((key) =>`${encodeURIComponent(key)}=${encodeURIComponent(params[key] ?? "")}`).join("&");
}/*** Taro 应用的路由工具类。** 提供页面跳转、重定向、切换 Tab、重启应用等方法,并支持可选的查询参数。** @property navigateTo - 跳转到指定页面,可携带查询参数。* @property redirectTo - 重定向到指定页面,可携带查询参数。* @property switchTab - 切换到指定 Tab 页面,可携带查询参数。* @property reLaunch - 重启应用到指定页面,可携带查询参数。** @example* router.navigateTo('/pages/home', { userId: 123 });*/
const router: Router = {push(url, params) {if (params) {const query = buildQuery(params);Taro.navigateTo({ url: `${url}?${query}` });} else {Taro.navigateTo({ url });}},replace(url, params) {if (params) {const query = buildQuery(params);Taro.redirectTo({ url: `${url}?${query}` });} else {Taro.redirectTo({ url });}},switchTab(url, params) {if (params) {const query = buildQuery(params);Taro.switchTab({ url: `${url}?${query}` });} else {Taro.switchTab({ url });}},reLaunch(url, params) {if (params) {const query = buildQuery(params);Taro.reLaunch({ url: `${url}?${query}` });} else {Taro.reLaunch({ url });}},
};export { router };

Footer.vue

<template><view class="footer"><nut-tabbarv-model="activeName"@tab-switch="tabSwitch":safe-area-inset-bottom="true"><nut-tabbar-itemv-for="item in list":key="item.name":name="item.name":tab-title="item.title":icon="item.icon"></nut-tabbar-item></nut-tabbar></view>
</template><script setup lang="ts">
import { Home, Category, My } from "@nutui/icons-vue-taro";
import { ref, h, computed } from "vue";
import { router } from "@/router";
import { useSystemOutsideStore } from "@/stores/modules/system";const useSystemStore = useSystemOutsideStore();
type TabItem = {name: string;path: string;title: string;icon: unknown;
};const list = ref<TabItem[]>([{ name: "home", path: "/pages/index/index", title: "首页", icon: h(Home) },{ name: "test", path: "/pages/test/test", title: "测试", icon: h(Category) },{ name: "my", path: "/pages/my/my", title: "我的", icon: h(My) },
]);const activeName = computed({get: () => useSystemStore.tabbar.active,set: (value) => useSystemStore.setActiveTab(value),
});const tabSwitch = (item: TabItem, index: number) => {const path = list.value.filter((tab) => tab.name === item.name)[0].path;console.log(path, index);router.switchTab(path);
};
</script>

Header.vue

<template><view class="header">header</view>
</template>

layout/index.vue

<template><nut-config-provider class="h-full" :theme-vars="themeVars"><view class="layout h-full flex flex-col"><view class="header" v-show="isShowHeader"><Header :title="title" /></view><view class="content flex-1"><slot /></view><view class="footer" v-show="isShowFooter"><Footer :activeName="footerActive" /></view></view></nut-config-provider>
</template><script setup lang="ts">
import Header from "@/components/Header.vue";
import Footer from "@/components/Footer.vue";
import { onMounted, ref } from "vue";type Props = {title: string;isShowHeader?: boolean;isShowFooter?: boolean;footerActive: string;
};
withDefaults(defineProps<Props>(), {title: "标题",isShowHeader: false,isShowFooter: true,footerActive: "home"
});// 修改nutui主题样式 https://nutui.jd.com/taro/vue/4x/#/zh-CN/component/configprovider
const themeVars = ref({primaryColor: "#008000",primaryColorEnd: "#008000",
});onMounted(() => {console.log("页面显示");
});
</script>

index/index.vue

<template><Layout title="首页" footerActive="home"><view>{{ msg }}</view><view class="text-red-500" @tap="testPinia">点我测试pinia{{ count }}</view><view className="text-[#acc855] text-[100px]">Hello world!</view><view class="outer"><view class="inner">嵌套样式测试</view><view class="w-[50%] h-5 bg-amber-400"></view></view><nut-button type="info">测试nutui</nut-button></Layout>
</template><script setup lang="ts">
import { ref, computed } from 'vue'
import './index.css'
import Layout from '@/layout/index.vue' // +
import { useCounterOutsideStore } from '@/stores/modules/demo'const msg = ref('Hello world')
const count = computed(() => counterStore.count)
const counterStore = useCounterOutsideStore()
const testPinia = () => {counterStore.increment()console.log(counterStore.count)
}
</script>

my/index.vue test/index.vue 和下面一样

<template><Layout title="我的" footerActive="my"><view class="my-page"><view class="my-header">我的</view><view class="my-content">欢迎来到我的页面</view></view></Layout>
</template><script setup lang="ts">
import "./my.css";
import Layout from "@/layout/index.vue";
</script>

src/app.config.ts

// https://docs.taro.zone/docs/app-config
export default defineAppConfig({pages: ['pages/index/index', 'pages/my/my','pages/test/test',],tabBar: {custom: true,list: [{ pagePath: 'pages/index/index', text: '首页' },{ pagePath: 'pages/test/test', text: '测试' },{ pagePath: 'pages/my/my', text: '我的' },]},window: {backgroundTextStyle: 'light',navigationBarBackgroundColor: '#fff',navigationBarTitleText: 'WeChat',navigationBarTextStyle: 'black'}
})

src/app.css

page{@apply h-full;
}

最终实现结果

请添加图片描述

项目地址

https://github.com/template-space/taro-template-vue

后续功能接入

🔳taro-axiosAPI 采用模块化导入方式
🔳上拉刷新、下拉加载
🔳子页面分包,跳转、拦截
🔳图片、视频、canvas、图表echarts
🔳地图
🔳…

敬请期待💥






到这里就结束了,后续还会更新 Taro、Vue 系列相关,还请持续关注!
感谢阅读,若有错误可以在下方评论区留言哦!!!

111



推荐文章👇

uniapp-vue3-vite 搭建小程序、H5 项目模板

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/95702.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/95702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ITU-R P.372 无线电噪声预测库调用方法

代码功能概述&#xff08;ITURNoise.c&#xff09;该代码是一个 ITU-R P.372 无线电噪声预测 的计算程序&#xff0c;能够基于 月份、时间、频率、地理位置、人为噪声水平 计算特定地点的 大气噪声、银河噪声、人为噪声及其总和&#xff0c;并以 CSV 或标准输出 方式提供结果。…

《从报错到运行:STM32G4 工程在 Keil 中的头文件配置与调试实战》

《从报错到运行&#xff1a;STM32G4 工程在 Keil 中的头文件配置与调试实战》文章提纲一、引言• 阐述 STM32G4 在嵌入式领域的应用价值&#xff0c;说明 Keil 是开发 STM32G4 工程的常用工具• 指出头文件配置是 STM32G4 工程在 Keil 中开发的关键基础环节&#xff0c;且…

Spring 事务提交成功后执行额外逻辑

1. 场景与要解决的问题在业务代码里&#xff0c;常见诉求是&#xff1a;只有当数据库事务真正提交成功后&#xff0c;才去执行某些“后置动作”&#xff0c;例如&#xff1a;发送 MQ、推送消息、写审计/埋点日志、刷新缓存、通知外部系统等。如果这些动作在事务提交前就执行&am…

Clickhouse MCP@Mac+Cherry Studio部署与调试

一、需求背景 已经部署测试了Mysql、Drois的MCP Server,想进一步测试Clickhouse MCP的表现。 二、环境 1)操作系统 MacOS+Apple芯片 2)Clickhouse v25.7.6.21-stable、Clickhouse MCP 0.1.11 3)工具Cherry Studio 1.5.7、Docker Desktop 4.43.2(199162) 4)Python 3.1…

Java Serializable 接口:明明就一个空的接口嘛

对于 Java 的序列化,我之前一直停留在最浅层次的认知上——把那个要序列化的类实现 Serializbale 接口就可以了嘛。 我似乎不愿意做更深入的研究,因为会用就行了嘛。 但随着时间的推移,见到 Serializbale 的次数越来越多,我便对它产生了浓厚的兴趣。是时候花点时间研究研…

野火STM32Modbus主机读取寄存器/线圈失败(三)-尝试将存贮事件的地方改成数组(非必要解决方案)(附源码)

背景 尽管crc校验正确了&#xff0c;也成功发送了EV_MASTER_EXECUTE事件&#xff0c;但是eMBMasterPoll( void )中总是接收的事件是EV_MASTER_FRAME_RECEIVED或者EV_MASTER_FRAME_SENT&#xff0c;一次都没有执行EV_MASTER_EXECUTE。EV_MASTER_EXECUTE事件被别的事件给覆盖了&…

微信小程序校园助手程序(源码+文档)

源码题目&#xff1a;微信小程序校园助手程序&#xff08;源码文档&#xff09;☑️ 文末联系获取&#xff08;含源码、技术文档&#xff09;博主简介&#xff1a;10年高级软件工程师、JAVA技术指导员、Python讲师、文章撰写修改专家、Springboot高级&#xff0c;欢迎高校老师、…

59-python中的类和对象、构造方法

1. 认识一下对象 世间万物皆是"对象" student_1{ "姓名":"小朴", "爱好":"唱、跳、主持" ......... }白纸填写太落伍了 设计表格填写先进一些些 终极目标是程序使用对象去组织数据程序中设计表格&#xff0c;我们称为 设计类…

向成电子惊艳亮相2025物联网展,携工控主板等系列产品引领智造新风向

2025年8月27-29日&#xff0c;IOTE 2025 第二十四届国际物联网展深圳站在深圳国际会展中心&#xff08;宝安&#xff09;盛大启幕&#xff01;作为全球规模领先的物联网盛会之一&#xff0c;本届展会以“生态智能&#xff0c;物联全球”为核心&#xff0c;汇聚超1000家全球头部…

阵列信号处理之均匀面阵波束合成方向图的绘制与特点解读

阵列信号处理之均匀面阵波束合成方向图的绘制与特点解读 文章目录前言一、方向图函数二、方向图绘制三、副瓣电平四、阵元个数对主瓣宽度的影响五、阵元间距对主瓣宽度的影响六、MATLAB源代码总结前言 \;\;\;\;\;均匀面阵&#xff08;Uniform Planar Array&#xff0c;UPA&…

算法在前端框架中的集成

引言 算法是前端开发中提升性能和用户体验的重要工具。随着 Web 应用复杂性的增加&#xff0c;现代前端框架如 React、Vue 和 Angular 提供了强大的工具集&#xff0c;使得将算法与框架特性&#xff08;如状态管理、虚拟 DOM 和组件化&#xff09;无缝集成成为可能。从排序算法…

网络爬虫是自动从互联网上采集数据的程序

网络爬虫是自动从互联网上采集数据的程序网络爬虫是自动从互联网上采集数据的程序&#xff0c;Python凭借其丰富的库生态系统和简洁语法&#xff0c;成为了爬虫开发的首选语言。本文将全面介绍如何使用Python构建高效、合规的网络爬虫。一、爬虫基础与工作原理 网络爬虫本质上是…

Qt Model/View/Delegate 架构详解

Qt Model/View/Delegate 架构详解 Qt的Model/View/Delegate架构是Qt框架中一个重要的设计模式&#xff0c;它实现了数据存储、数据显示和数据编辑的分离。这种架构不仅提高了代码的可维护性和可重用性&#xff0c;还提供了极大的灵活性。 1. 架构概述 Model/View/Delegate架构将…

光谱相机在手机行业的应用

在手机行业&#xff0c;光谱相机技术通过提升拍照色彩表现和扩展健康监测等功能&#xff0c;正推动摄像头产业链升级&#xff0c;并有望在AR/VR、生物医疗等领域实现更广泛应用。以下为具体应用场景及技术突破的详细说明&#xff1a;‌一、光谱相机在手机行业的应用场景‌‌拍照…

FASTMCP中的Resources和Templates

Resources 给 MCP 客户端/LLM 读取的数据端点&#xff08;只读、按 URI 索引、像“虚拟文件系统”或“HTTP GET”&#xff09;&#xff1b; Templates 可带参数的资源路由&#xff08;URI 里占位符 → 运行函数动态生成内容&#xff09;。 快速要点 • 用途&#xff1a;把文件…

OpenBMC之编译加速篇

加快 OpenBMC 的编译速度是一个非常重要的话题,因为完整的构建通常非常耗时(在高性能机器上也需要数十分钟,普通电脑上可能长达数小时)。以下是从不同层面优化编译速度的详细策略,您可以根据自身情况组合使用。 一、核心方法:利用 BitBake 的缓存和共享机制(效果最显著…

Kafka面试精讲 Day 8:日志清理与数据保留策略

【Kafka面试精讲 Day 8】日志清理与数据保留策略 在Kafka的高吞吐、持久化消息系统中&#xff0c;日志清理与数据保留策略是决定系统资源利用效率、数据可用性与合规性的关键机制。作为“Kafka面试精讲”系列的第8天&#xff0c;本文聚焦于日志清理机制&#xff08;Log Cleani…

基于Hadoop的网约车公司数据分析系统设计(代码+数据库+LW)

摘 要 本系统基于Hadoop平台&#xff0c;旨在为网约车公司提供一个高效的数据分析解决方案。随着网约车行业的快速发展&#xff0c;平台上产生的数据量日益增加&#xff0c;传统的数据处理方式已无法满足需求。因此&#xff0c;设计了一种基于Hadoop的大规模数据处理和分析方…

Python反向迭代完全指南:从基础到高性能系统设计

引言&#xff1a;反向迭代的核心价值在数据处理和算法实现中&#xff0c;反向迭代是解决复杂问题的关键技术。根据2024年Python开发者调查报告&#xff1a;85%的链表操作需要反向迭代78%的时间序列分析依赖反向处理92%的树结构遍历需要后序/逆序访问65%的加密算法使用反向计算P…

ClickHouse使用Docker部署

OLTP和OLAP介绍基本业务量到达分库分表量级&#xff0c;则离不开数据大屏、推荐系统、画像系统等搭建&#xff0c;需要搭建以上系统&#xff0c;则离不开海量数据进行存储-分析-统计。 而海量数据下 TB、PB级别数据存储&#xff0c;靠Mysql进行存储-分析-统计无疑是灾难。所以就…