【unibest】uniapp + vue3 超实用模板(续)

5,740 阅读5分钟

系列文章

6、引入 unocss

pnpm add -D @unocss/preset-uno unocss-applet

然后配置 unocss.config.ts

// uno.config.ts
import {
  Preset,
  defineConfig,
  presetAttributify,
  presetIcons,
  transformerDirectives,
  transformerVariantGroup,
} from 'unocss'

import {
  presetApplet,
  presetRemRpx,
  transformerApplet,
  transformerAttributify,
} from 'unocss-applet'

const isH5 = process.env?.UNI_PLATFORM === 'h5'
const isMp = process.env?.UNI_PLATFORM?.startsWith('mp') ?? false

const presets: Preset[] = []
if (!isMp) {
  /**
   * you can add `presetAttributify()` here to enable unocss attributify mode prompt
   * although preset is not working for applet, but will generate useless css
   * 为了不生产无用的css,要过滤掉 applet
   */
  // 支持css class属性化,eg: `<button bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600" text="sm white">attributify Button</button>`
  presets.push(presetAttributify())
}
export default defineConfig({
  presets: [
    presetApplet({ enable: !isH5 }),
    presetRemRpx(),
    ...presets,
    // 支持图标,需要搭配图标库,eg: @iconify-json/carbon, 使用 `<button class="i-carbon-sun dark:i-carbon-moon" />`
    presetIcons({
      scale: 1.2,
      warn: true,
      extraProperties: {
        display: 'inline-block',
        'vertical-align': 'middle',
      },
    }),
  ],
  /**
   * 自定义快捷语句
   * @see https://github.com/unocss/unocss#shortcuts
   */
  shortcuts: [['center', 'flex justify-center items-center']],
  transformers: [
    // 启用 @apply 功能
    transformerDirectives(),
    // 启用 () 分组功能
    // 支持css class组合,eg: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
    transformerVariantGroup(),
    // Don't change the following order
    transformerAttributify({
      // 解决与第三方框架样式冲突问题
      prefixedOnly: true,
      prefix: 'fg',
    }),
    transformerApplet(),
  ],
  rules: [
    [
      'p-safe',
      {
        padding:
          'env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)',
      },
    ],
    ['pt-safe', { 'padding-top': 'env(safe-area-inset-top)' }],
    ['pb-safe', { 'padding-bottom': 'env(safe-area-inset-bottom)' }],
  ],
})

/**
 * 最终这一套组合下来会得到:
 * mp 里面:mt-4 => margin-top: 32rpx
 * h5 里面:mt-4 => margin-top: 1rem
 */

到这里还没完,页面上写 class="mt-4" 还不生效,接着做下面的动作:

  • src/main.ts 增加 import 'virtual:uno.css'
  • vite.config.ts 文件写入:
import UnoCSS from 'unocss/vite'

// 其他配置省略
plugins: [UnoCSS()],

8、引入 vite-plugin-uni-pages

在 Vite 驱动的 uni-app 上使用基于文件的路由系统。

8-1 安装 @uni-helper/vite-plugin-uni-pages

pnpm i -D @uni-helper/vite-plugin-uni-pages

8-2 配置vite.config.ts

// vite.config.ts
import { defineConfig } from 'vite'
import Uni from '@dcloudio/vite-plugin-uni'
import UniPages from '@uni-helper/vite-plugin-uni-pages'

// It is recommended to put it in front of Uni
export default defineConfig({
  plugins: [UniPages(), Uni()],
})

8-3 配置 pages.config.ts

// pages.config.ts
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'

export default defineUniPages({
  // 你也可以定义 pages 字段,它具有最高的优先级。
  pages: [],
  globalStyle: {
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '@uni-helper',
  },
})

8-4 使用 route 标签配置路由信息

通过 pnpm dev:h5 发现 pages.json 被重写了。 此时可以在页面上(仅限src/pages里面的页面)增加 route block 来配置,如:

route block

以后每个页面”要不要导航栏,标题要怎么写,标题样式要怎样“都可以在这里设置了,再也不用来回横切了,爽歪歪!

9、vite-plugin-uni-layouts

考虑到我们可能会有多套布局,tabbar页面,非tabbar页面,通屏页面,非通屏页面。对于通屏页面,需要自己实现导航栏,同时还要刘海的情况。我们不用在每个页面都引入某个类似布局的组件,这样太浪费生产力了,我们通过 vite-plugin-uni-layouts 可以声明式地设定使用哪个布局,爽歪歪。

9-1 安装 @uni-helper/vite-plugin-uni-layouts

pnpm i -D @uni-helper/vite-plugin-uni-layouts

9-2 配置vite.config.ts

// vite.config.ts
import { defineConfig } from 'vite'
import Uni from '@dcloudio/vite-plugin-uni'
import UniPages from '@uni-helper/vite-plugin-uni-pages'
import UniLayouts from '@uni-helper/vite-plugin-uni-layouts'

// It is recommended to put it in front of Uni
export default defineConfig({
  plugins: [UniPages(), UniLayouts(), Uni()],
})

9-3 在 src/layouts 里面创建布局

layouts

9-4 使用layout

在页面上的route-block增加 layout 属性,设置想用的 layout,可选值是所有的 src/layouts里面的文件名,我这里是 "default"|"home".

layout声明

重新运行,就可以看到效果了。

10、request 请求拦截器

10-1 先写好 store, 请求需要使用其中的token

src/store/user.ts

// src/store/user.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { UserInfo } from '../typings'

export const useUserStore = defineStore(
  'user',
  () => {
    const userInfo = ref<UserInfo>()

    const setUserInfo = (val: UserInfo) => {
      userInfo.value = val
    }

    const clearUserInfo = () => {
      userInfo.value = undefined
    }

    return {
      userInfo,
      setUserInfo,
      clearUserInfo,
    }
  },
  {
    persist: true,
  },
)

src/store/index.ts

// src/store/index.ts
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化

const store = createPinia()
store.use(
  createPersistedState({
    storage: {
      getItem: uni.getStorageSync,
      setItem: uni.setStorageSync,
    },
  }),
)

export default store

// 模块统一导出
export * from './user'
export * from './count'

10-2 请求拦截,支持设定返回类型

// src/utils/http.ts
/* eslint-disable no-param-reassign */
import { useUserStore } from '@/store'
import { UserInfo } from '@/typings'

const userStore = useUserStore()
type Data<T> = {
  code: number
  msg: string
  result: T
}

// 请求基地址
const baseURL = 'http://localhost:5565/api'

// 拦截器配置
const httpInterceptor = {
  // 拦截前触发
  invoke(options: UniApp.RequestOptions) {
    // 1. 非 http 开头需拼接地址
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
    }
    // 2. 请求超时
    options.timeout = 10000 // 10s
    // 3. 添加小程序端请求头标识
    options.header = {
      platform: 'mp-weixin', // 可选值与 uniapp 定义的平台一致,告诉后台来源
      ...options.header,
    }
    // 4. 添加 token 请求头标识
    const { token } = userStore.userInfo as unknown as UserInfo
    if (token) {
      options.header.Authorization = `Bearer ${token}`
    }
  },
}

// 拦截 request 请求
uni.addInterceptor('request', httpInterceptor)
// 拦截 uploadFile 文件上传
uni.addInterceptor('uploadFile', httpInterceptor)

export const http = <T>(options: UniApp.RequestOptions) => {
  // 1. 返回 Promise 对象
  return new Promise<Data<T>>((resolve, reject) => {
    uni.request({
      ...options,
      // 响应成功
      success(res) {
        // 状态码 2xx,参考 axios 的设计
        if (res.statusCode >= 200 && res.statusCode < 300) {
          // 2.1 提取核心数据 res.data
          resolve(res.data as Data<T>)
        } else if (res.statusCode === 401) {
          // 401错误  -> 清理用户信息,跳转到登录页
          userStore.clearUserInfo()
          uni.navigateTo({ url: '/pages/login/login' })
          reject(res)
        } else {
          // 其他错误 -> 根据后端错误信息轻提示
          uni.showToast({
            icon: 'none',
            title: (res.data as Data<T>).msg || '请求错误',
          })
          reject(res)
        }
      },
      // 响应失败
      fail(err) {
        uni.showToast({
          icon: 'none',
          title: '网络错误,换个网络试试',
        })
        reject(err)
      },
    })
  })
}

export default http

注意:上面代码设定,如果返回 401 则去 /pages/login/login 也,请根据需要添加该页面或修改逻辑。

10-3 使用requst,可设定返回类型

const handleRequest = () => {
  const res = http<UserItem[]>({
    url: '/getUserList',
    method: 'GET',
  })
  console.log(res)
}

11、多环境处理

11-1 编写env文件夹,里面放.env,.env.production,.env.development等文件

env

11-2 把http.ts文件的baseUrl 替换掉

// 请求基地址
- const baseURL = 'http://localhost:5565/api'
+ const baseURL = import.meta.env.VITE_SERVER_BASEURL

src/env-d.ts

/// <reference types="vite/client" />
/// <reference types="vite-svg-loader" />

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}
// 下面都是新增的
interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  readonly VITE_SERVER_PORT: string
  readonly VITE_SERVER_BASEURL: string
  readonly VITE_DELETE_CONSOLE: string
  // 更多环境变量...
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

11-3 vite.config.ts 针对console是否清楚的处理

首先需要 pnpm i -D terser,然后加入如下代码。

// vite.config.ts
build: {
  minify: 'terser',
  terserOptions: {
    compress: {
      drop_console: env.VITE_DELETE_CONSOLE === 'true',
      drop_debugger: env.VITE_DELETE_CONSOLE === 'true',
    },
  },
}

12、unocss icons 的使用

12-1 安装对应的库

安装格式如下: pnpm i -D @iconify-json/[the-collection-you-want]

这里我安装carbon, 所以执行 pnpm i -D @iconify-json/carbon,如果还要其他的还可以继续安装。

12-2 使用

<button class="i-carbon-sun dark:i-carbon-moon" />

而且可以在编辑器就可以预览,体验很好。 unocss-icon

所有的图标都在 icones.js.org/ 这个网站,要啥就安装啥(而且不用科学上网,利好国人)。

https://icones.js.org/

13、引入uni-ui

13-1 安装

pnpm i -S @dcloudio/uni-ui
pnpm i -D @uni-helper/uni-ui-types

13-2 tsconfig.ts 增加类型

"types": [
  "@dcloudio/types",
  "@types/wechat-miniprogram",
  "@uni-helper/uni-app-types",
+  "@uni-helper/uni-ui-types"
]

13-3 pages.config.ts 配置 easycom

easycom: {
  autoscan: true,
  custom: {
    // uni-ui 规则如下配置
    '^uni-(.*)': '@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
  },
},

13-4 使用

<uni-icons type="contact" size="30"></uni-icons>
<uni-badge text="1"></uni-badge>

可以看到页面立马生效了。

14、自己写的常用组件,可以通过easycom引入

14-1 pages.config.ts 配置 easycom

easycom: {
  autoscan: true,
  custom: {
+    // 以 Fly 开头的组件,在 components 文件夹中查找引入(需要重启服务器)
+    '^Fly(.*)': '@/components/fly-$1/fly-$1.vue',
+    '^fly-(.*)': '@/components/fly-$1/fly-$1.vue',
    // uni-ui 规则如下配置
    '^uni-(.*)': '@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue',
  },
},

虽然上面2种写法都支持,但是最好还是与主流框架统一,全部使用小写+中划线。

14-2 使用

<fly-header></fly-header>
<FlyHeader></FlyHeader>

可以看到页面立马生效了。

虽然上面2种写法都支持,但是最好还是与主流框架统一,全部使用小写+中划线。

完结,撒花~~

unibest 链接地址

最后还是贴几个链接,不然你们想要的都找不到~~

文档地址:unibest.tech/ (2024年11月搞定的域名)

[unibest.tech] 对应的是:feige996/unibest-docs 同一仓库生成的内容。

github 地址:github.com/feige996/un…

gitee 地址:gitee.com/feige996/un…


旧的文档地址(留个纪念):codercup/unibest-docs

微信交流群 因不能贴引流二维码,有需要的同学请看官方文档微信群 。

image.png

好文推荐