1. 环境安装
bash
npm install -g @vue/cli
2. 创建项目
bash
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project
3. 运行项目(以h5为例)
arduino
npm install
npm run dev:H5
4. ui框架
4.1 wot-design-uni
文档地址: wot-design-uni.cn/
npm install wot-design-uni
4.2 如何使用
方式一:easycom组件规范 (推荐)
传统vue组件,需要安装、引用、注册,三个步骤后才能使用组件。easycom将其精简为一步,如果不了解easycom,可先查看 官网文档 。
在 pages.json
中 添加配置,确保路径引入正确:
php
// pages.json
{
"easycom": {
"autoscan": true,
"custom": {
"^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue"
}
},
// 此为本身已有的内容
"pages": [
// ......
]
}
方式二: 基于vite配置自动引入组件 (个人不推荐)
如果不熟悉easycom
,也可以通过@uni-helper/vite-plugin-uni-components实现组件的自动引入。
- 推荐使用@uni-helper/[email protected]及以上版本,因为在0.0.9版本开始其内置了
wot-design-uni
的resolver
。
- 如果使用此方案时控制台打印很多
Sourcemap for points to missing source files
,可以尝试将vite版本升级至4.5.x
以上版本。
css
npm i @uni-helper/vite-plugin-uni-components -D
javascript
// vite.config.ts
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import Components from '@uni-helper/vite-plugin-uni-components'
import { WotResolver } from '@uni-helper/vite-plugin-uni-components/resolvers'
export default defineConfig({
plugins: [
// make sure put it before `Uni()`
Components({
resolvers: [WotResolver()]
}), uni()],
});
如果你使用 pnpm
,请在根目录下创建一个 .npmrc
文件,参见issue。
java
// .npmrc
public-hoist-pattern[]=@vue*
// or
// shamefully-hoist = true
4.3. 安装sass
Wot Design Uni
依赖 sass
,因此在使用之前需要确认项目中是否已经安装了 sass
,如果没有安装,可以通过以下命令进行安装:
css
npm install [email protected] -D
Dart Sass 3.0.0
废弃了一批API,而组件库目前还未兼容,因此请确保你的sass
版本为1.78.0
及之前的版本。
重新运行项目,在页面上引入一个按钮测试一下
5. 请求库
5.1. 请求封装
typescript
const targetUrl = "https://xxx.com/api";
class Request {
private baseUrl: string;
private defaultHeaders: { [key: string]: string };
private config: { [key: string]: any };
// 构造函数
constructor(baseUrl?: string, defaultHeaders?: { [key: string]: string }) {
this.baseUrl = baseUrl || targetUrl;
this.defaultHeaders = {
"Content-Type": "application/json",
...defaultHeaders,
};
this.config = {
method: "GET",
dataType: "json",
responseType: "text",
timeout: 10000,
};
}
// 设置请求头
private setHeaders(headers: { [key: string]: string }) {
// 合并默认请求头和传入的请求头
const mergedHeaders = Object.assign({}, this.defaultHeaders, headers || {});
// 获取本地存储的ticket或从store中获取
const ticket = uni.getStorageSync("ticket");
if (ticket) {
mergedHeaders["ticket"] = ticket;
}
return mergedHeaders;
}
// 请求拦截器
private requestInterceptor(config: any) {
config.header = this.setHeaders(config?.header || {});
config.url = this.baseUrl + config.url;
return config;
}
// 响应拦截器
private responseInterceptor(response: any) {
if (response.statusCode === 401) {
uni.navigateTo({ url: "/pages/user/login" });
throw new Error("用户未登录");
}
if (response.statusCode >= 200 && response.statusCode < 300) {
return response.data.data;
} else {
throw new Error(`请求失败,状态码:${response.statusCode}`);
}
}
// 统一错误处理
private handleError(error: Error) {
uni.showToast({
icon: "none",
title: error.message,
});
console.error(error);
throw error;
}
// 请求方法
private request(options: any) {
const mergedConfig = { ...this.config, ...options };
const interceptedRequestConfig = this.requestInterceptor(mergedConfig);
return new Promise((resolve, reject) => {
uni.request({
...interceptedRequestConfig,
success: (res) => {
try {
const data = this.responseInterceptor(res);
resolve(data);
} catch (error) {
this.handleError(error);
}
},
fail: (err) => {
this.handleError(new Error("网络请求失败"));
},
});
});
}
public get(url: string, options: any = {}) {
return this.request({ url, method: "GET", ...options });
}
public post(url: string, data?: any, options: any = {}) {
return this.request({ url, method: "POST", data, ...options });
}
public put(url: string, data?: any, options: any = {}) {
return this.request({ url, method: "PUT", data, ...options });
}
public delete(url: string, data?: any, options: any = {}) {
return this.request({ url, method: "DELETE", data, ...options });
}
}
const http = new Request();
export default http;
5.2. 使用示例
javascript
import http from '@/request/http';
export const getListApi = () => {
return http.get("/test/banner");
};
5.3. 使用vue-request管理接口示例
vbscript
npm install vue-request
xml
<template>
<view class="content">
<wd-button @click="refresh">测试刷新api</wd-button>
<text>列表</text>
<view v-if="loading">加载中...</view>
<template v-else>
<view v-for="item in list" :key="item._id">
<text>{{ item.url }}</text>
</view>
</template>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { getListApi } from "@/api/wallpaper";
// // 使用useRequest
import { useRequest } from 'vue-request';
const { data: list, runAsync: getList, loading, refresh } = useRequest(getListApi);
// 不使用useRequest
// const list = ref([])
// const loading = ref(false)
// const getList = async () => {
// loading.value = true
// const res = await getListApi().finally(() => {
// loading.value = false
// })
// list.value = res
// }
// const refresh = () => {
// getList()
// }
</script>
6. 状态管理
6.1. 安装pinia
npm install pinia
6.2. 安装持久化
npm install pinia-plugin-unistorage
也可以选择使用 pinia-plugin-persistedstate
6.3. 在src下创建一个store文件夹并创建 index.ts 文件
javascript
import { createPinia } from "pinia";
import { createUnistorage } from "pinia-plugin-unistorage";
const pinia = createPinia();
pinia.use(createUnistorage());
export default pinia;
6.4. 在main.ts中引入
javascript
import { createSSRApp } from "vue";
import App from "./App.vue";
import pinia from '@/store' // 从store文件夹中引入
export function createApp() {
const app = createSSRApp(App);
app.use(pinia) // 使用
return {
app,
};
}
6.5. 测试缓存效果
- 在store下创建 user.ts 文件
javascript
// useUserStore.ts
import { defineStore } from "pinia";
import { ref } from "vue";
export const useUserStore = defineStore(
"userStore",
() => {
const userInfo = ref({
name: "Tom",
age: 18,
});
const getUserInfo = async () => {
setTimeout(() => {
// 随机返回一个用户信息
// 生成一个随机数,拼接到用户信息中
const random = Math.floor(Math.random() * 100);
userInfo.value = {
name: `Tom${random}`,
age: random,
};
});
};
return {
userInfo,
getUserInfo,
};
},
{
unistorage: true, // 开启后对 state 的数据读写都将持久化
}
);
- 在页面上使用
xml
<template>
<view class="content">
<image class="logo" src="/static/logo.png" />
<view class="text-area">
<text class="title">{{ title }}</text>
<text class="title">{{ userInfo.name }}</text>
</view>
<wd-button @click="getUserInfo">获取用户信息</wd-button>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const title = ref('zero-starter')
import { useUserStore } from "@/store/user";
import { storeToRefs } from "pinia";
const userStore = useUserStore()
const { userInfo } = storeToRefs(useUserStore());
// 测试store缓存,用户信息
const getUserInfo = () => {
userStore.getUserInfo()
}
</script>
7. 提效神器
7.1. autoprefixer
autoprefixer
是一个自动为 CSS 添加浏览器前缀的工具,可以确保你的 CSS 在不同浏览器中兼容。在 vite.config.ts
中添加 autoprefixer
插件配置如下:
TypeScript复制
php
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
export default defineConfig({
plugins: [uni()],
css: {
postcss: {
plugins: [
require("autoprefixer")(),
],
},
},
});
插件效果 :当你在项目中编写 CSS 代码时,autoprefixer
会自动检测你的 CSS 属性,并根据目标浏览器的兼容性要求,为这些属性添加必要的浏览器前缀。例如,如果你编写了 transform: rotate(45deg);
,它可能会自动转换为以下代码,以确保在不同浏览器中都能正确显示:
css
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
7.2. postcss-px-to-viewport-8-plugin
postcss-px-to-viewport-8-plugin
是一个将 px 单位转换为视口单位(如 vw、vh、vmin 等)的插件,有助于实现响应式设计。在 vite.config.ts
中添加该插件配置如下:
javascript
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
export default defineConfig({
plugins: [uni()],
css: {
postcss: {
plugins: [
require("postcss-px-to-viewport-8-plugin")({
unitToConvert: "px", // 需要转换的单位,默认为"px"
viewportWidth: 750, // 视窗的宽度,对应设计稿的宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ["*"], // 能转化为视口单位的属性列表
viewportUnit: "vmin", // 希望使用的视口单位
fontViewportUnit: "vmin", // 字体使用的视口单位
selectorBlackList: ["is-checked"], // 需要忽略的CSS选择器
minPixelValue: 1, // 设置最小的转换数值
mediaQuery: false, // 媒体查询里的单位是否需要转换
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: /(/|\)(node_modules|uni_modules)(/|\)/, // 忽略某些文件夹下的文件
}),
],
},
},
});
插件效果 :当你在项目中编写以 px
为单位的 CSS 属性时,该插件会根据配置自动将其转换为视口单位。例如,如果你编写了以下代码:
css复制
css
.element {
width: 100px;
height: 50px;
font-size: 16px;
}
它可能会被转换为:
css复制
css
.element {
width: 13.33333vmin;
height: 6.66667vmin;
font-size: 2.13333vmin;
}
这样,当用户在不同设备上查看页面时,元素的尺寸会根据视口大小自动调整,从而实现更好的响应式效果。
7.3. unplugin-auto-import
arduino
npm i -D unplugin-auto-import
在 vite.config.ts中添加 AutoImport 配置
javascript
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from "unplugin-auto-import/vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
uni(),
AutoImport({
include: [
/.[tj]sx?$/, // .ts, .tsx, .js, .jsx
/.vue$/,
/.vue?vue/, // .vue
],
imports: [
"vue",
"uni-app",
"pinia",
{
"vue-request": ["useRequest"],
},
],
dts: "./auto-imports.d.ts",
dirs: ["./src/utils/**"], // 自动导入 utils 中的方法
eslintrc: {
enabled: false, // Default `false`
// provide path ending with `.mjs` or `.cjs` to generate the file with the respective format
filepath: "./.eslintrc-auto-import.json", // Default `./.eslintrc-auto-import.json`
globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
},
vueTemplate: true, // default false
}),
],
css: {
postcss: {
plugins: [
require("postcss-px-to-viewport-8-plugin")({
unitToConvert: "px", // 需要转换的单位,默认为"px"
viewportWidth: 750, // 视窗的宽度,对应pc设计稿的宽度,一般是1920
// viewportHeight: 1080,// 视窗的高度,对应的是我们设计稿的高度
unitPrecision: 5, // 单位转换后保留的精度
propList: [
// 能转化为vw的属性列表
"*",
],
viewportUnit: "vmin", // 希望使用的视口单位
fontViewportUnit: "vmin", // 字体使用的视口单位
selectorBlackList: ["is-checked"], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。cretae
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: /(/|\)(node_modules|uni_modules)(/|\)/, // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
}),
require("autoprefixer")(),
],
},
},
});
7.3.1 可能存在的问题
1. 解决eslint报错,在tsconfig.json文件的 "include"数组中添加 "./auto-imports.d.ts"
perl
{
"extends": "@vue/tsconfig/tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"lib": ["esnext", "dom"],
"types": ["@dcloudio/types"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue","./auto-imports.d.ts"]
}
8. unocss
8.1. 安装
css
npm i unocss unocss-applet -D
8.2. 配置uno.config.ts
在根目录新建 uno.config.ts
php
// uno.config.ts
import {
type Preset,
type SourceCodeTransformer,
presetUno,
defineConfig,
presetAttributify,
presetIcons,
transformerDirectives,
transformerVariantGroup,
} from 'unocss'
import {
presetApplet,
presetRemRpx,
transformerApplet,
transformerAttributify,
} from 'unocss-applet'
// @see https://unocss.dev/presets/legacy-compat
// import presetLegacyCompat from '@unocss/preset-legacy-compat'
const isMp = process.env?.UNI_PLATFORM?.startsWith('mp') ?? false
const presets: Preset[] = []
const transformers: SourceCodeTransformer[] = []
if (isMp) {
// 使用小程序预设
presets.push(presetApplet(), presetRemRpx())
transformers.push(transformerApplet())
} else {
presets.push(
// 非小程序用官方预设
presetUno(),
// 支持css class属性化
presetAttributify(),
)
}
export default defineConfig({
presets: [
...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',
},
}),
// 将颜色函数 (rgb()和hsl()) 从空格分隔转换为逗号分隔,更好的兼容性app端,example:
// `rgb(255 0 0)` -> `rgb(255, 0, 0)`
// `rgba(255 0 0 / 0.5)` -> `rgba(255, 0, 0, 0.5)`
// presetLegacyCompat({
// commaStyleColorFunction: true,
// }) as Preset,
],
/**
* 自定义快捷语句
* @see https://github.com/unocss/unocss#shortcuts
*/
shortcuts: [
['center', 'flex justify-center items-center'],
['text-primary', 'text-yellow'],
],
transformers: [
...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',
}),
],
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
*/
8.3 引入
- 在 main.ts中增加
arduino
import 'virtual:uno.css'
- 在vite.config.ts中增加
javascript
import UnoCSS from 'unocss/vite'
export default defineConfig({
plugins: [
uni(),
UnoCSS(),
]
// 省略其他配置项
})
以上写可能会出现报错,原因如下: unocss0.59.x已经不支持commonjs了,仅仅支持ESM(只使用 ESM 来管理模块依赖,不再支持 CommonJS 或 AMD 等其他模块化方案),可以查看《unocss的发布变更日志》:
解决方法:
javascript
import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from "unplugin-auto-import/vite";
export default async () => {
const UnoCSS = (await import('unocss/vite')).default
return defineConfig({
plugins: [
uni(),
UnoCSS(),
...
8.4 安装 iconify
在使用 iconify
之前需要安装对应的图标库,安装格式如下:
npm i -D @iconify-json/[the-collection-you-want]
这里选择的是 carbon
执行 npm i -D @iconify-json/carbon
即可。
8.4.1 如何使用
ini
<view class="i-carbon-user-avatar"/>
<view class="i-carbon-hexagon-outline text-orange"/>
8.5 unocss相关文档
9. 工具库
9.1 安装常用工具库 dayjs , radash
npm install dayjs radash
10. 统一管理路由跳转
10.1. 安装qs
bash
npm install qs
npm install @types/qs -D
10.2. 封装
- 在src目录下新建 utils 文件夹
- 在utils新建index.ts
typescript
import qs from 'qs';
export const showLoading = (title?: string, options?: UniNamespace.ShowLoadingOptions) => {
uni.showLoading({ title, mask: true, ...options })
}
export const hideLoading = () => {
uni.hideLoading()
}
export const showToast = (title: string, options?: UniNamespace.ShowToastOptions) => {
uni.showToast({ title, icon: 'none', duration: 1800, mask: false, ...options })
}
export const appendQueryString = (url: string, obj?: Record<string, any>): string => {
// 检查对象是否为空
if (!obj || Object.keys(obj).length === 0) {
return url;
}
const queryString = qs.stringify(obj);
// 检查URL是否已经包含查询字符串
const separator = url.includes('?') ? '&' : '?';
return `${url}${separator}${queryString}`;
}
10.3. 统一页面跳转管理,在utils新建router.ts
ini
import { throttle } from "radash";
type ParamsType = Record<string, string | number>;
type RedirectType = "push" | "replace" | "reload" | "switchTab";
const redirect = throttle(
{
interval: 500,
},
(url: string, options?: {
params?: ParamsType;
type?: RedirectType;
} & Omit<UniNamespace.NavigateToOptions, "url">) => {
const params = options?.params;
const type = options?.type || "push";
// 可以在此增加跳转拦截
if (type === "push") {
return uni.navigateTo({
url: appendQueryString(url, params),
...options,
});
}
if (type === "replace") {
return uni.redirectTo({
url: appendQueryString(url, params),
...options,
});
}
if (type === "reload") {
return uni.reLaunch({
url: appendQueryString(url, params),
...options,
});
}
if (type === "switchTab") {
return uni.switchTab({
url: appendQueryString(url, params),
...options,
});
}
}
);
export const $$goBack = (
delta: number = 1,
options?: UniNamespace.NavigateBackOptions
) => {
return uni
.navigateBack({
delta,
...options,
})
.catch((err) => {
return $$goHome();
});
};
export const $$goHome = () => {
return uni.switchTab({
url: "/pages/index/index",
});
};
export const $$goWebview = (targetUrl: string) => {
return redirect("/pages/webview/index", {
params: {
url: targetUrl,
},
});
};
11. 公共样式
11.1. 在src目录下新建styles文件夹
11.2. 在styles下新建common.scss
css
:not(not) {
/* *所有选择器 参考 https://ask.dcloud.net.cn/article/36055 */
box-sizing: border-box;
}
// 重置微信小程序的按钮样式
button {
background-color: transparent;
}
button:after {
border: none;
}
image {
display: block;
}
page {
font-size: 32px;
color: #333333;
background: #f8f8f8;
}
11.3. 在uni.scss中引入
scss
@import "@/styles/common.scss";
常见问题
1. 用 VsCode
开发 uni-app
项目时,报错JSON 中不允许有注释
用 VsCode
开发 uni-app
项目时,我们打开 pages.json
和 manifest.json
,发现会报红,这是因为在 json
中是不能写注释的,而在 jsonc
是可以写注释的。
jsonc
的 c
就是 comment
【注释】的意思。
解决方案
我们把 pages.json
和 manifest.json
这两个文件指定使用 jsonc
的语法即可,然后就以写注释了。在设置中打开 settings.json
,添加配置:
json
// 配置语言的文件关联
"files.associations": {
"pages.json": "jsonc",
"manifest.json": "jsonc",
},
误区
千万不要把所有 json 文件都关联到 jsonc 中,你感觉在 json 中都能写注释了,比以前更好用了,其实不然,json 就是 json,jsonc 就是 jsonc,严格 json 文件写了注释就会报错。
例如,package.json
写了注释在编译的时候,是会报错的,因为 package.json
就是严格 json 文件,不能写注释。