Write the Code. Change the World.

分类目录
8月 11

项目体积比较大,或使用的插件体积比较大,小程序分包就很有必要。

https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html

配置方法

假设支持分包的小程序目录结构如下:

├── app.js
├── app.json
├── app.wxss
├── packageA
│   └── pages
│       ├── cat
│       └── dog
├── packageB
│   └── pages
│       ├── apple
│       └── banana
├── pages
│   ├── index
│   └── logs
└── utils

开发者通过在 app.json subPackages 字段声明项目分包结构:

{
  "pages":[
    "pages/index",
    "pages/logs"
  ],
  "subPackages": [
    {
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ],
      "entry": "index.js"
    }, {
      "root": "packageB",
      "name": "pack2",
      "pages": [
        "pages/apple",
        "pages/banana"
      ]
    }
  ]
}

当然,分包文件也可以在根的 pages 目录下,记得定义好 root 就好。root 下的所有页面都是属于分包的。 还有,插件的配置。如果想插件也在分包内使用,必须也配置进去,否则插件会打包在主包里。
如:

{
    ……,
    "subPackages": [
        {
            "root": "packageA",
            "pages": [
                "pages/cat",
                "pages/dog"
            ],
            "plugins": {
                "kivicube": {
                    "version": "2.16.19",
                    "provider": "wx3bbab3920eabccb2"
                }
            }
        }
    ]
}
7月 29

以下是一个基于 Nuxt 4 架构面向复杂企业级后台系统的完整项目目录结构示例,结合了官方推荐、社区实践与真实项目经验,具备高度可扩展性和清晰的职责划分:

my-nuxt4-project/
├── app/                          # 所有客户端代码集中目录(Nuxt 4 新结构)
│   ├── assets/                   # 需构建的静态资源(scss、字体、图片等)
│   │   ├── css/
│   │   │   └── main.scss
│   │   └── fonts/
│   ├── components/               # 自动导入的 Vue 组件
│   │   ├── ui/                   # 基础 UI 组件(按钮、表单、弹窗)
│   │   ├── layout/               # 布局相关组件(Sidebar、Header、Footer)
│   │   └── charts/               # 图表组件(ECharts、D3)
│   ├── composables/              # 自动导入的 Composition API 逻辑
│   │   ├── useAuth.ts            # 权限处理
│   │   ├── useApi.ts             # 统一 API 调用封装
│   │   └── useTable.ts           # 列表页通用逻辑
│   ├── layouts/                  # 页面级布局
│   │   ├── default.vue           # 默认布局(含侧边栏、顶部栏)
│   │   ├── auth.vue              # 登录/注册页布局
│   │   └── blank.vue             # 空白布局(弹窗、预览页)
│   ├── middleware/               # 路由中间件
│   │   ├── auth.global.ts        # 全局鉴权
│   │   ├── role.ts               # 路由级角色控制
│   │   └── log.ts                # 路由访问日志
│   ├── pages/                    # 文件系统路由
│   │   ├── index.vue             # 首页(重定向到 /dashboard)
│   │   ├── login.vue
│   │   ├── dashboard.vue
│   │   ├── system/
│   │   │   ├── user.vue
│   │   │   ├── role.vue
│   │   │   └── dept.vue
│   │   └── […slug].vue           # 404 或其他通配路由
│   ├── plugins/                  # 客户端插件
│   │   ├── vuetify.client.ts     # UI 框架初始化
│   │   ├── dayjs.client.ts       # 日期库
│   │   └── pinia.ts              # 状态管理
│   ├── stores/                   # Pinia 全局状态
│   │   ├── auth.ts
│   │   ├── app.ts                # 全局设置、主题
│   │   └── menu.ts               # 动态菜单
│   ├── utils/                    # 通用工具函数
│   │   ├── request.ts            # axios 二次封装
│   │   ├── validate.ts           # 表单校验规则
│   │   └── constants.ts          # 常量枚举
│   ├── app.vue                   # 应用根组件(全局入口)
│   ├── app.config.ts             # 运行时配置(主题、i18n)
│   └── error.vue                 # 错误页(500/404)

├── shared/                       # 前后端共享代码(类型、DTO、工具)
│   ├── types/
│   │   ├── api.d.ts
│   │   └── user.d.ts
│   └── utils/
│       └── date.ts

├── server/                       # 服务端代码(Nitro)
│   ├── api/                      # RESTful API
│   │   ├── auth/
│   │   │   ├── login.post.ts
│   │   │   └── logout.post.ts
│   │   ├── system/
│   │   │   ├── user.get.ts
│   │   │   └── user.post.ts
│   ├── middleware/               # 服务端中间件(日志、鉴权)
│   │   └── logger.ts
│   ├── plugins/                  # 服务端插件(数据库连接)
│   │   └── db.ts
│   ├── utils/                    # 服务端工具(加密、权限)
│   │   └── jwt.ts
│   └── routes/                   # 非 /api 前缀的自定义路由
│       └── healthz.ts            # /healthz

├── public/                       # 纯静态资源(favicon、robots.txt)
│   ├── favicon.ico
│   └── images/
│       └── logo.png

├── modules/                      # 本地开发的 Nuxt 模块
│   └── nuxt-module-i18n/

├── layers/                       # 多应用共享层(微前端、主题系统)
│   └── admin-layer/

├── tests/                        # 单元 & e2e 测试
│   ├── unit/
│   └── e2e/

├── .env                          # 环境变量
├── .env.example
├── .gitignore
├── nuxt.config.ts                # 主配置文件
├── package.json
├── tsconfig.json                 # 一个 tsconfig 即可(Nuxt 4 特性)
└── README.md

✅ 目录亮点说明

目录/文件 作用与最佳实践
app/ Nuxt 4 强制的新结构,所有客户端代码集中,IDE 感知更清晰,监听更快
shared/ 前后端共享类型、DTO、工具,避免重复定义
server/ 基于 Nitro 的服务端逻辑,支持热更新、无服务器部署
modules/ 本地私有模块,便于拆分业务、发布内部包
layers/ 多应用/主题继承,适合微前端或 SaaS 白标系统

✅ 迁移建议

  • 旧项目想升级?
    运行 npx nuxt upgrade --dedupe 即可平滑升级;目录结构不强制迁移,旧结构也能跑 。
  • 新项目建议直接采用上述结构,提前享受更快的 HMR、类型推断和 IDE 体验 。

如需进一步模板,可直接使用官方 nuxt-ui-pro/dashboard 作为起点,再按此结构扩展。

7月 29

nuxt 的一些搞法。

  • 路由别名。比如你想访问 www.xxx.com 默认对应代码的 ./app/pages/index.vue./app.vue 文件。如果想对应 ./app/pages/home/index.vue,路由别名就很有用处了。

只需要在 ./app/pages/home/index.vue 中使用路由别名就好。如:

<template>
    <h2>home</h2>
</template>

<script setup lang="ts">
definePageMeta({
  alias: '/'
})
</script>

这种方法,访问地址依然是 www.xxx.com。 初了使用路由别名,还可以使用中间件和redirect 的方式来实现。其实,这两种方式都是 redirect 。但是这个时候地址变成了 www.xxx.com/home 。方法如下。

方法一: ./app/pages/index.vue 依然存在,通过 definePageMeta 来做跳转。

// pages/index.vue
<script setup lang="ts">
definePageMeta({
  redirect: '/home'
})
</script>

方法二: 新建中间件 ./app/middleware/redirectRoot.ts 文件。加入以下代码

export default defineNuxtRouteMiddleware(() => {
  if (useRoute().path === '/') {
    return navigateTo('/home')
  }
})

然后在 nuxt.config.ts 中加入以下代码

export default defineNuxtConfig({
  …,
  nitro: {
    routeRules: {
      '/': { redirect: '/home' }
    }
  }
})

看个人喜好,个人还是喜欢用路由别名,然后是中间件。确实有强迫症,不喜欢多一个 ./app/index.vue 文件。

7月 25

以整个视口为单位,使用滑轮滚动或导航触发来达到展示效果的网站是一种需求。一种是自己手写 js + css 来达到这种效果。还有一种使用 fullpage.js 来达到这个效果。当然,使用 swiperjs 来实现这效果也是妥妥的赞,还很丝滑。

https://github.com/alvarotrigo/fullPage.js

https://swiperjs.com/get-started

主要是 fullpage.js 是需要花钱购买服务,而 swiperjs 是开源使用的。并且 swiperjs 不仅仅可以做这种视口滚动效果。

所以,这里选择使用 swiperjs 来完成目标。

DEMO 展示效果如下图所示(gif 使用 https://www.mnggiflab.com/ 录制和压缩):

基础环境

初始化一个 vue 项目。

pnpm create vue

cd swiperFullpage

pnpm install 

pnpm format

pnpm dev

创建选项如下图所示。

然后,创建一个版本管理。

git init

git add .

git commit -m 'initialize'

先去掉 components 里的页面和默认样式。只留 App.vue 文件

rm -rf ./src/components
rm -rf ./src/assets

然后,修改 App.vue 文件。

<template>
  <h2>Home</h2>
</template>

删除 main.ts 中的样式引入。运行起来看看。没问题,添加 git 版本控制。

git add . && git commit -m '删除默认页面和样式'

使用

先安装 swiper

pnpm add swiper

开始在 App.vue 中编写相关的代码

<template>
  <swiper
    direction="vertical"
    :modules="[FreeMode, Mousewheel]"
    :space-between="0"
    :slides-per-view="'auto'"
    :allow-touch-move="false"
    :pagination="{ clickable: true }"
    :mousewheel="{ forceToAxis: true, sensitivity: 10, releaseOnEdges: true }"
    style="height: 100vh"
    class="swiper-pointer-events"
    @swiper="onSwiper"
    @slideChange="onSlideChange"
    ref="swiperRef"
  >
    <swiper-slide class="slide" style="background: #444">
      <div class="slide-content">
        <h2>点绛唇·屏却相思</h2>
        <p>屏却相思,近来知道都无益</p>
        <p>不成抛掷,梦里终相觅</p>
        <p>醒后楼台,与梦俱明灭</p>
        <p>西窗白,纷纷凉月,一院丁香雪</p>
      </div>
    </swiper-slide>

    <swiper-slide class="slide" style="background: #333">
      <div class="slide-content">
        <h2>《望江南》</h2>
        <p>多少恨,昨夜梦魂中</p>
        <p>还似旧时游上苑</p>
        <p>车如流水马如龙</p>
        <p>花月正春风</p>
      </div>
    </swiper-slide>

    <swiper-slide class="slide" style="background: #222">
      <div class="slide-content">
        <h2>《蟾宫曲·春情》</h2>
        <p>平生不会相思</p>
        <p>才会相思,便害相思</p>
        <p>身似浮萍,心如飞絮,气若游丝,</p>
        <p>空一缕余香在此</p>
      </div>
    </swiper-slide>

    <swiper-slide class="slide slide-footer" style="height: 240px; background: #111">
      <p>© 2025 Vini123.Com All rights reserved.</p>
    </swiper-slide>
  </swiper>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Swiper, SwiperSlide } from 'swiper/vue'
import { FreeMode, Mousewheel } from 'swiper/modules'
import 'swiper/css'

const swiperRef = ref()
const activeIndex = ref(0)

const onSwiper = (swiper: any) => {
  swiperRef.value = swiper
  activeIndex.value = swiper.activeIndex
}
const onSlideChange = () => {
  if (swiperRef.value) {
    activeIndex.value = swiperRef.value.activeIndex
  }
}
</script>

<style>
html,
body {
  margin: 0;
  padding: 0;
}
</style>

<style scoped>
.slide {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

.slide-footer {
  color: #fff;
  align-items: flex-end;
  padding-bottom: 20px;
}

.slide-content {
  color: #fff;
  text-align: center;
  background: rgba(0, 0, 0, 0.4);
  padding: 30px 100px;
  border-radius: 24px;
}
</style>

这里要注意一点。并不是所有的页面都是整个视口的高度。比如底部的信息。这个时候需要设置 slides-per-view:autoFreeMode。其他的根据实际情况操作。这里页面高度是整个视口的高度 100vh,有的时候,swiper 是某个页面的一部分,高度自然不一样。

运行起来看看,提交版本控制。

git add .
git commit -m '安装 swiper,编写 demo'
7月 10

后台管理系统,个人审美还是更喜欢 element-plus 多一点。如果组件更丰富点就更好了。

https://element-plus.org/zh-CN/guide/installation.html

安装

# 安装
pnpm add element-plus

# 自动导入,安装unplugin-vue-components 和 unplugin-auto-import 这两款插件
pnpm add -D unplugin-vue-components unplugin-auto-import

配置自动导入
编辑 vite.config.ts

import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  // ...
  plugins: [
    // ...
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

追加 AutoImportComponents 配置。完整的配置文件如下:

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vite.dev/config/
export default defineConfig({
  base: '/admin/',
  build: {
    outDir: 'admin',
    emptyOutDir: true,
    chunkSizeWarningLimit: 3000,
    rollupOptions: {
      output: {
        entryFileNames: 'assets/[name].[hash].js', // 入口文件名
        chunkFileNames: 'assets/[name].[hash].js', // chunk 文件名
        assetFileNames: 'assets/[name].[hash].[ext]', // 静态资源文件名
      },
    },
  },
  plugins: [
    vue(),
    vueDevTools(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
})

下边进行尝试使用

使用

编辑 src/views/home/index.vue,增加按钮组件,尝试下效果。

<template>
  <div>
    <h1>HI, {{ user.name }} , Welcome to the Home Page</h1>
    <h2>Email: {{ user.email }}</h2>
    <h2>Api: {{ api }}</h2>
    <div>
      <el-button type="primary" @click="test">Click Me</el-button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from 'vue'

const user = ref<User>({
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  createdAt: new Date(),
})

const api = ref(import.meta.env.VITE_API_BASE_URL)

function test() {
  alert('Button Clicked!')
}
</script>

<style scoped>
h1 {
  color: #42b983;
  font-size: 2em;
}
</style>

运行起来,效果如下图所示。

7月 09

vite7: https://vite.dev/blog/announcing-vite7

node: https://nodejs.org/en/download

创建初始化

现在创建一个叫 forgetting 的项目

pnpm self-update

pnpm create vue@latest

# 引导构建

┌  Vue.js - The Progressive JavaScript Framework
│
◇  请输入项目名称:
│  forgetting
│
◇  请选择要包含的功能: (↑/↓ 切换,空格选择,a 全选,回车确认)
│  TypeScript, Router(单页面应用开发), Pinia(状态管理), ESLint(错误预防), Prettier(代码格式化)
│
◇  选择要包含的试验特性: (↑/↓ 切换,空格选择,a 全选,回车确认)
│  Oxlint(试验阶段), rolldown-vite(试验阶段)

正在初始化项目 C:\Users\Windows\Desktop\study\vue\forgetting...
│
└  项目初始化完成,可执行以下命令:

创建完成后,执行下边命令,运行起来。

   cd forgetting
   pnpm install
   pnpm format
   pnpm dev

提交版本。

git init -b main
git add .
git commit -m 'initialize'

删除默认页面,组件,样式

rm -rf ./src/views/*
rm -rf ./src/components/*
rm -rf ./src/assets/logo.svg

新增一个首页,运行起来

touch ./src/views/home/index.vue

# 添加以下内容
<template>
    <div>
        <h1>Welcome to the Home Page</h1>
    </div>
</template>

修改 App.vue

<template>
  <RouterView />
</template>

调整路由 router.ts

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('../views/home/index.vue'),
    },
  ],
})

export default router

去掉 assets/main.css 中的其他代码,保留。

@import './base.css';

#app {
  margin: 0;
  padding: 0;
  font-weight: normal;
}

a,
.green {
  text-decoration: none;
  color: hsla(160, 100%, 37%, 1);
  transition: 0.4s;
  padding: 3px;
}

重新运行

pnpm dev

> forgetting@0.0.0 dev C:\Users\Windows\Desktop\study\vue\forgetting
> vite

Port 5173 is in use, trying another one...

  ROLLDOWN-VITE v7.0.6  ready in 858 ms

  ➜  Local:   http://localhost:5174/
  ➜  Network: use --host to expose
  ➜  Vue DevTools: Open http://localhost:5174/__devtools__/ as a separate window
  ➜  Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools
  ➜  press h + enter to show help

可以执行代码检查

pnpm lint

> forgetting@0.0.0 lint C:\Users\Windows\Desktop\study\vue\forgetting
> run-s lint:*

> forgetting@0.0.0 lint:oxlint C:\Users\Windows\Desktop\study\vue\forgetting
> oxlint . --fix -D correctness --ignore-path .gitignore

Found 0 warnings and 0 errors.
Finished in 12ms on 8 files with 87 rules using 16 threads.

> forgetting@0.0.0 lint:eslint C:\Users\Windows\Desktop\study\vue\forgetting
> eslint . --fix

C:\Users\Windows\Desktop\study\vue\forgetting\src\views\home\index.vue
  1:1  error  Component name "index" should always be multi-word  vue/multi-word-component-names

✖ 1 problem (1 error, 0 warnings)

 ELIFECYCLE  Command failed with exit code 1.
ERROR: "lint:eslint" exited with 1.
 ELIFECYCLE  Command failed with exit code 1.

官方默认的vue文件名是以大写字母开头,多单词的形式。或以中横线连接。个人喜欢以名字+动词的形式来定义文件。即以单数名字作为文件夹名,动词作为文件名。比如,想要一个创建订单的页面可以这样:order/create.vue,如果想要一个订单列表的页面就是 order/list.vue。 这种规则和默认规则相冲,eslint 检查出错误来。所以可以手动修改该策略。

对于组件命名,还是喜欢用大写字母开头,多单词的形式。 components/MediaPlayer/MediaPlayer.vue

修改 eslint.config.ts,增加规则来允许上边所说的消息单个单词的文件命名。

  {
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  }

需要扩展和覆盖规则都可以在这里进行。因为是刚创建的项目,所以配置很干净除了新增这一条,其他都是默认的,完整如下:

import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import pluginVue from 'eslint-plugin-vue'
import pluginOxlint from 'eslint-plugin-oxlint'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup

export default defineConfigWithVueTs(
  {
    name: 'app/files-to-lint',
    files: ['**/*.{ts,mts,tsx,vue}'],
  },

  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),

  pluginVue.configs['flat/essential'],
  vueTsConfigs.recommended,
  ...pluginOxlint.configs['flat/recommended'],
  skipFormatting,
  {
    rules: {
      'vue/multi-word-component-names': 'off',
    },
  }
)

再执行 pnpm lint 就不会报错了。

执行 pnpm format 格式化代码。中间不规范操作,都可以通过这个来自动纠正。

提交版本。 git add . && git commit -m '删除默认页面、组件、样式,配置支持单个单词文件名的eslint规则'

7月 03

https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps

https://vite-pwa-org-zh.netlify.app/guide/

渐进式 Web 应用(Progressive Web App,PWA)是一个使用 web 平台技术构建的应用程序,但它提供的用户体验就像一个特定平台的应用程序。

它像网站一样,PWA 可以通过一个代码库在多个平台和设备上运行。它也像一个特定平台的应用程序一样,可以安装在设备上,可以离线和在后台运行,并且可以与设备和其他已安装的应用程序集成。

特点

  1. 体验类似原生应用,而不是浏览器打开的网站。、
  2. 离线使用部分功能。

学习参考

https://juejin.cn/post/7497868344223989794

https://juejin.cn/post/7294554207096750090

https://ionic.nodejs.cn/vue/pwa