技术选型纠结了一周,真正搭起来只要一天。
选型这件事,最重要的不是"哪个最强",而是"哪个最适合你"。
这篇带你拆解一个塔罗牌小程序的技术骨架,每个配置文件背后都有一个"为什么"。
🤔 你是不是也在技术选型上纠结过?
打开 Google 搜"小程序框架推荐",出来一堆结果:Taro、uni-app、mpvue、WePY……
每个框架都有人说好,每个框架都有人踩坑。你收藏了 10 篇对比文章,越看越迷糊,最后原地踏步三天,一个项目都没新建。
说实话,我也经历过这个阶段。框架选型最大的坑不是选错,而是永远在选,永远不动手。
后来我想通了:框架只是工具,能跑起来的才是好框架。
于是我花了半天时间调研,一天时间搭骨架,第二天就开始写业务代码了。今天把整个过程拆给你看。
💻我是爱折腾的编程爱好者,喜欢研究AI 辅助开发的各种实践,热爱分享踩坑后的收获与思考,也享受用代码写出各种实用小工具解决问题的快乐。
如果你也在技术这条路上向前走,关注我,愿我们能彼此陪伴,一起成为更好的自己 🌱
🎯 这篇文章能帮你解决什么
读完你会收获:
🔹 理解 UniApp + Vue3 + TS + TailwindCSS 的选型逻辑🔹 看懂每个配置文件背后的设计意图🔹 知道哪些配置"绝对不能动",哪些可以自由调整🔹 获得一个可以直接复用的项目骨架模板
🧰 技术选型:为什么是这套组合?
好,咱们先来聊聊选型。不讲虚的,直接说结论。
🔹 框架:UniApp
对比了 Taro 和 UniApp,最终选了 UniApp,原因很简单:
说人话就是: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: SpreadTypequestion: stringinterpretation: 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 默认用 rem。weapp-tailwindcss 插件会自动把 rem 转成 rpx,但转换规则要自己配置:
// vite.config.tsWeappTailwindcss({rem2rpx: true,})
不配这个的话,你在模板里写 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="text-accent-gold") | |
color: $accent-gold) | |
additionalData 注入) |
别纠结"为什么定义两遍",能用就行。
💡 我到底该不该用这套技术栈?
适合你的情况:
🔹 想同时做微信小程序和 H5🔹 团队(或你自己)有 Vue 基础🔹 想要一套代码多端运行,减少维护成本🔹 项目不太复杂(不是超大型 App)
可能不太适合的情况:
🔹 只做微信小程序,不做 H5(原生开发更轻量)🔹 需要极致性能(原生 > 框架)🔹 团队全是 React 基础(选 Taro 更顺手)
最后啰嗦一句:
技术选型这件事,没有"最好",只有"最适合"。与其花一周时间对比框架,不如花一天搭骨架、一天写业务。
代码是跑出来的,不是比出来的。
🎁 总结一下
这篇文章拆解了一个小程序项目的技术骨架:UniApp 做跨平台、TypeScript 做类型安全、TailwindCSS 做样式、Pinia 做状态管理。
每个配置文件背后都有一个"为什么"——shamefully-hoist=true 是因为 UniApp 的依赖解析机制,preflight: false 是因为小程序不支持伪元素,SCSS 变量和 TailwindCSS 颜色"重复定义"是因为各司其职。
下一篇会聊 AI 接入:怎么让塔罗牌"活"起来,以及为什么我从 Cloudflare Workers 迁移到了 Google Gemini。
如果你觉得这篇拆解有点用,别藏着掖着,点赞收藏加关注,防止下次想找的时候找不到~
你在技术选型上还踩过哪些坑?评论区唠一唠,一起交流才是真朋友 🎯
夜雨聆风