头图

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

接着上篇文章【2023】uniapp+vue3+ts超实用模板,我们继续优化模板。

6、处理unocss生成的样式在小程序报错问题

在上面模板的基础上,npm run dev:mp-weixin 会在dist/dev/mp-weixin生成微信小程序文件,打开微信开发者工具,导入该文件夹,发现跑不起来,控制台报错,如下。

小程序-报错
发现是生成的 app.wxss 包含了通配符*, 微信小程序不认识,编译不通过,页面也无法显示。

解决方案:删除unocss里面的自带预设presetUno,改用@uni-helper/unocss-preset-uni的专门针对uniapp的预设presetUni,代码如下:

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

+ import { presetUni } from '@uni-helper/unocss-preset-uni'

export default defineConfig({
  presets: [
-    presetUno,
+    // @ts-expect-error 类型兼容性
+    presetUni(),
    // 支持css class属性化,eg: `<button bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600" text="sm white">attributify Button</button>`
    presetAttributify(),
    // 支持图标,需要搭配图标库,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',
      },
    }),
  ],
  transformers: [
    transformerDirectives(),
    // 支持css class组合,eg: `<div class="hover:(bg-gray-400 font-medium) font-(light mono)">测试 unocss</div>`
    transformerVariantGroup(),
  ],
})

重新 npm run dev:mp-weixin, 会发现生成的 app.wxss, 去掉了 * 增加了page 标签。

生成的app.wxss

现在微信小程序也正常显示了,如下图

小程序-正常

我们来处理一下生成的 app.wxsspage标签 不认识的问题,

app.wxss page标签报错

.stylelintrc.cjs 的 rules 中增加如下代码,然后重启vscode即可。

{
  rules: {
    // 其他配置
+    // 处理小程序page标签不认识的问题
+    'selector-type-no-unknown': [
+      true,
+      {
+        ignoreTypes: ['page'],
+      },
+    ],
  }
}

mac电脑 cmd + shift + p, 出现下面图,找到 重启扩展宿主

app.wxss不报错了

7、处理 v-bind() 在小程序中不生效问题

vue3 可以在css中使用v-bind(v-bind in css)来绑定变量,这是一个很不错的特性,我们来看下面的例子。

v-bind()使用

小程序报错,样式不生效

解决方法:在 manifest.json 里面增加如下设置:

{
  "mp-weixin": {
+    "styleIsolation": "shared",
  }
}

小程序生效了

这样生效的前提是,style标签不能加scoped, 否则依然报错。另外一个问题是,该文件引入的子组件的样式会被污染。

鉴于我们一般会加上scoped,并且不想因为样式污染导致样式问题,最终还是决定用js的方式处理。(尴尬了)把上述提交revert掉即可。

另外附上小程序样式隔离文档:微信开发者文档-组件样式隔离

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

所有的图标都在 https://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种写法都支持,但是最好还是与主流框架统一,全部使用小写+中划线。

完结

写了这么多功能,需要的基本都加上了,还有没加上的,环境评论区留言。

项目地址:github: fly-vue3-templates/vue3-uniapp-template

项目地址:gitee: fly-vue3-templates/vue3-uniapp-template

本文由mdnice多平台发布


斐鸽传书
7 声望0 粉丝