乐于分享
好东西不私藏

�� 一天搭出小程序骨架:UniApp + Vue3 + TS + TailwindCSS

�� 一天搭出小程序骨架:UniApp + Vue3 + TS + TailwindCSS

技术选型纠结了一周,真正搭起来只要一天。

选型这件事,最重要的不是"哪个最强",而是"哪个最适合你"。

这篇带你拆解一个塔罗牌小程序的技术骨架,每个配置文件背后都有一个"为什么"


🤔 你是不是也在技术选型上纠结过?

打开 Google 搜"小程序框架推荐",出来一堆结果:Taro、uni-app、mpvue、WePY……

每个框架都有人说好,每个框架都有人踩坑。你收藏了 10 篇对比文章,越看越迷糊,最后原地踏步三天,一个项目都没新建。

说实话,我也经历过这个阶段。框架选型最大的坑不是选错,而是永远在选,永远不动手。

后来我想通了:框架只是工具,能跑起来的才是好框架。

于是我花了半天时间调研,一天时间搭骨架,第二天就开始写业务代码了。今天把整个过程拆给你看。


💻我是爱折腾的编程爱好者,喜欢研究AI 辅助开发的各种实践,热爱分享踩坑后的收获与思考,也享受用代码写出各种实用小工具解决问题的快乐。

如果你也在技术这条路上向前走,关注我,愿我们能彼此陪伴,一起成为更好的自己 🌱


🎯 这篇文章能帮你解决什么

读完你会收获:

🔹 理解 UniApp + Vue3 + TS + TailwindCSS 的选型逻辑🔹 看懂每个配置文件背后的设计意图🔹 知道哪些配置"绝对不能动",哪些可以自由调整🔹 获得一个可以直接复用的项目骨架模板


🧰 技术选型:为什么是这套组合?

好,咱们先来聊聊选型。不讲虚的,直接说结论。

🔹 框架:UniApp

对比了 Taro 和 UniApp,最终选了 UniApp,原因很简单:

对比项
Taro
UniApp
跨平台
微信/支付宝/H5/RN
微信/支付宝/H5/20+平台
生态
京东维护,社区活跃
DCloud 维护,插件市场丰富
文档
偏开发者向
小白友好,示例多
踩坑成本
中等
较低(社区踩坑多,解决方案多)

说人话就是:UniApp 的"坑"已经被前人踩得差不多了,遇到问题 Google 一搜就有答案。对个人开发者来说,社区成熟度 > 技术先进性

🔹 语言:TypeScript

你可能会问:"小程序用 JavaScript 不行吗?"

行,但** TypeScript 能帮你提前发现 80% 的低级错误**。

比如你拼错了属性名,JavaScript 不会报错,运行时才炸;TypeScript 在你写代码的时候就画红线。对个人开发者来说,TypeScript 不是增加负担,而是减少 debug 时间。

🔹 样式:TailwindCSS

在小程序里用 TailwindCSS 需要额外适配(weapp-tailwindcss),但好处是巨大的

🔹 不用想类名(class="flex items-center justify-between" 比 class="header-layout" 清晰多了)🔹 样式统一(颜色、间距、圆角全部用 Tailwind 的 token)🔹 响应式天然支持

🔹 包管理:pnpm

这个没有选择余地。UniApp + pnpm 是标配,npm 和 yarn 会有依赖解析问题。记住这一点就行。


🛠️ 项目骨架拆解:41 个文件各干了啥

初始提交一次性生成了 41 个文件,但核心的其实就这几个。咱们一个一个拆。

🔹 vite.config.ts — 项目的"大脑"

这个文件控制着整个构建过程,有几个关键配置:

// SCSS 全局注入:每个组件都能直接用变量,不用 importcss: { preprocessorOptions: { scss: { additionalData: '@import ”@/styles/variables.scss”;', }, },},

这个配置的意思是:你在任何 .vue 文件里直接写 $bg-primary,就能用到 variables.scss 里定义的颜色,不用每页都 import 一遍。

还有一个自定义插件 injectAppidPlugin,它的作用是构建时自动把 AppID 写入 project.config.json,不用手动改代码。这个后面第 5 篇踩坑篇会详细讲。

🔹 .npmrc — 千万别删的一行

shamefully-hoist=true

这行的作用是让 pnpm 把所有依赖平铺到 node_modules,而不是 pnpm 默认的隔离模式。

为什么要这么做?因为 UniApp 的某些内部模块依赖绝对路径引用,如果用 pnpm 的严格隔离,依赖会找不到,然后你会看到一堆 Module not found 错误。

踩过的人都懂,没踩过的人迟早会踩。记住:这行不能删。

🔹 tsconfig.json — strict: true 的意义

{ ”compilerOptions”: { ”strict”: true, ”target”: ”ESNext”, ”moduleResolution”: ”bundler” }}

strict: true 开启了所有严格的类型检查。你可能会觉得"这也太严了",但严格带来的好处是巨大的

🔹 拼错属性名?编译就报错,不用等运行时🔹 函数参数类型不对?红线提醒🔹 空值未处理?强制你处理

对个人开发者来说,TypeScript 的严格模式就是免费的 code review。

🔹 tailwind.config.js — 小程序适配

TailwindCSS 在小程序里用,有几个关键配置:

module.exports = { corePlugins: { // 小程序不支持 ::before/::after,必须关闭 preflight: false, },}

preflight: false 是必须的。Tailwind 的 preflight(重置样式)用了大量的 ::before 和 ::after 伪元素,但微信小程序不支持这些。不关掉的话,你的样式会全乱套。

另外我还自定义了颜色体系,把塔罗牌的主题色全部定义好了:

colors: { 'bg-primary''#0f0f23'// 深色背景 'accent-gold''#c9a96e'// 金色强调 'accent-purple''#7b2d8e'// 紫色逆位 // ...}

🔹 src/styles/variables.scss — 设计 Token

除了 TailwindCSS,我还定义了一份 SCSS 变量,用于组件内需要动态计算的场景:

// 颜色$bg-primary: #0f0f23;$accent-gold: #c9a96e;// 尺寸$navbar-height: 88rpx;$tabbar-height: 100rpx;// 动画$transition-fast: 0.2s ease;$transition-normal: 0.3s ease;

为什么要两套(Tailwind + SCSS)?

TailwindCSS 适合写静态样式(class 绑定),SCSS 变量适合需要动态计算的场景(比如 JS 里读取高度值)。两套互补,不冲突。

🔹 pages.json — 路由和 TabBar

这个文件定义了页面路由和底部导航栏:

{ ”tabBar”: { ”custom”: true, ”list”: [ { ”pagePath”: ”pages/index/index”, ”text”: ”首页” }, { ”pagePath”: ”pages/draw/draw”, ”text”: ”抽牌” }, { ”pagePath”: ”pages/cards/cards”, ”text”: ”牌库” }, { ”pagePath”: ”pages/profile/profile”, ”text”: ”我的” } ] }}

注意 "custom": true——这意味着 TabBar 是自定义组件,不是原生的。为什么要自定义?因为原生 TabBar 的样式限制太多了,想改个图标大小都费劲。

easycom 也在这里配置:

{ ”easycom”: { ”custom”: { ”^T-(.*)”: ”@/components/TarotCard/$1.vue” } }}

这意味着你写 <T-Card /> 时,UniApp 会自动找到 src/components/TarotCard/Card.vue,不用手动 import。

🔹 src/store/tarot.ts — Pinia 状态管理

Pinia 是 Vue3 推荐的状态管理库(替代 Vuex)。这里管理着整个应用的核心状态:

// 当前占卜结果const currentReading = ref<{ cards: DrawnCard[] spreadType: SpreadType question: string interpretation: string // ...} | null>(null)// 历史记录const records = ref<ReadingRecord[]>([])

关键设计:历史记录最多保留 100 条,用 uni.setStorageSync 持久化到本地。

// 保存到本地存储function saveRecords() { uni.setStorageSync('tarot-records'JSON.stringify(records.value))}

为什么不直接存 records?因为 uni.setStorageSync 只能存字符串,所以要先 JSON.stringify。读取时再 JSON.parse 回来。


⚠️ 几个容易踩的坑

🔹 TailwindCSS 的 rpx 单位问题

小程序用 rpx 做响应式单位,但 TailwindCSS 默认用 remweapp-tailwindcss 插件会自动把 rem 转成 rpx,但转换规则要自己配置

// vite.config.tsWeappTailwindcss({ rem2rpxtrue,})

不配这个的话,你在模板里写 p-4(对应 1rem),小程序里会变成 1rem 而不是 32rpx样式会完全乱掉

🔹 @ 路径别名

tsconfig.json 里配了 @/* → ./src/*,但这在小程序里不一定生效。UniApp 的解析和 TypeScript 的路径解析是两套系统。

好消息是 UniApp 已经内置了 @/ 的支持,所以你在 .vue 文件里写 import xxx from '@/utils/xxx' 是没问题的。但不要自己加额外的路径别名,容易出问题。

🔹 scss 变量和 TailwindCSS 颜色重复

你可能注意到了,variables.scss 和 tailwind.config.js 里都定义了颜色。这不是重复,而是各司其职

场景
用哪个
模板 class 绑定
TailwindCSS(class="text-accent-gold"
CSS 里的动态计算
SCSS 变量(color: $accent-gold
JS 里读取颜色值
SCSS 变量(通过 additionalData 注入)

别纠结"为什么定义两遍",能用就行。


💡 我到底该不该用这套技术栈?

适合你的情况:

🔹 想同时做微信小程序和 H5🔹 团队(或你自己)有 Vue 基础🔹 想要一套代码多端运行,减少维护成本🔹 项目不太复杂(不是超大型 App)

可能不太适合的情况:

🔹 只做微信小程序,不做 H5(原生开发更轻量)🔹 需要极致性能(原生 > 框架)🔹 团队全是 React 基础(选 Taro 更顺手)

最后啰嗦一句:

技术选型这件事,没有"最好",只有"最适合"。与其花一周时间对比框架,不如花一天搭骨架、一天写业务。

代码是跑出来的,不是比出来的。


🎁 总结一下

这篇文章拆解了一个小程序项目的技术骨架:UniApp 做跨平台、TypeScript 做类型安全、TailwindCSS 做样式、Pinia 做状态管理。

每个配置文件背后都有一个"为什么"——shamefully-hoist=true 是因为 UniApp 的依赖解析机制,preflight: false 是因为小程序不支持伪元素,SCSS 变量和 TailwindCSS 颜色"重复定义"是因为各司其职。

下一篇会聊 AI 接入:怎么让塔罗牌"活"起来,以及为什么我从 Cloudflare Workers 迁移到了 Google Gemini。

如果你觉得这篇拆解有点用,别藏着掖着,点赞收藏加关注,防止下次想找的时候找不到~

你在技术选型上还踩过哪些坑?评论区唠一唠,一起交流才是真朋友 🎯