vue3+Vite4+pinia+uniapp+typeScript

547 阅读4分钟

一.项目初始化

uniapp官方网站

1.创建uniapp,vue3.0项目
  • 创建以 javascript 开发的工程(如命令行创建失败,请直接访问 gitee 下载模板)
npx degit dcloudio/uni-preset-vue#vite my-vue3-project
npx degit dcloudio/uni-preset-vue#vite-alpha my-vue3-project
  • 创建以 typescript 开发的工程(如命令行创建失败,请直接访问 gitee 下载模板)
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project

命令行创建项目vue3.0+typescript+vite如图所示:

  • 我用的是创建以 typescript 开发的工程
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project

image.png

  • 也可以直接访问gitee去下载模板:

image.png

  • 用命令行创建好了项目,里面的目录有,如图所示:

image.png

2.安装依赖
npm i/cnpm i/pnpm i都可以 我喜欢用pnpm

image.png

启动到内置浏览器运行:

image.png

启动到微信开发者工具:

image.png

启动到抖音开发者工具:

image.png

引用scss样式
pnpm install sass node-sass sass-loader@10 -D

image.png

3.语法糖插件

解决 import {ref,reactive ...} from 'vue'大量引入的问题

配置后可以不用引入,直接使用

pnpm i -D unplugin-auto-import

image.png

  • vite.config.js
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({
              imports:['vue','vue-router']
      })
      ],
});
4.解决@路径问题

vite.config.js (增加path,alias配置)

import { defineConfig } from "vite";
import uni from "@dcloudio/vite-plugin-uni";
import AutoImport from 'unplugin-auto-import/vite';
const path = require('path');
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
	  uni(),
	  AutoImport({
		  imports:['vue','vue-router']
	  })
	  ],
	  resolve:{
              //配置路径别名
              alias:{
                      '@':path.resolve(__dirname,'./src')
              }
	  }
});
5.代码格式化
5.1、导入插件

ext.dcloud.net.cn/plugin?id=7…

image.png

点击右侧使用HbuilderX导入插件按钮,插件会导入到HbuilderX中

HbuilderX ->工具->设置->插件配置 中可以按到相关插件

tips:在这里可以配置每次默认生成的.prettierrc.js内容

image.png 点开:打开文件.prettierrc.js进行配置 image.png

5.2、设置以后只选一个为prettier格式化代码

HbuilderX ->工具->设置->编译器设置中取消勾选 保存时自动化格式化,避免和官方格式化冲突

image.png 在文件中按下ctrl+S会触发,并且会在项目目录下自动生成配置文件。

1.prettierrc.js

2.prettierignore

image.png

.prettierignore文件中增加一个node_modules,node_modules也不需要格式化

image.png

5.3、vue文件双分栏 快捷键配置

image.png 配置后在vue文件中ctrl+shift+k即可生效

5.4、嵌套注释 在任意文件中按下ctrl+/会出现选择菜单,设置以后中选一个为嵌套注释。

image.png

# formatAndSave官方

image.png

image.png

6.启动,运行到h5,小程序。

image.png

也可以在内置中输入 pnpm run :mp-weixin
7.引入ui组价
可选的一些组件
  • uView
  • First UI
  • uview-plus
  • uv-ui
  • Vant
  • ThorUI
  • tmui.desin
  • uni-ui
7.1、安装uni-ui

官方提供的ui,可通过uni_modules导入全部组件。 ext.dcloud.net.cn/plugin?id=5…

7.2、安装uview-plus

Hbuilder X 方式:uiadmin.net/uview-plus/…

pnpm install sass sass-loader@10 -D
pnpm install dayjs clipboard

配置见官方文档 uiadmin.net/uview-plus/…

  • main.js
  • uni.scss
  • App.vue
  • pages.json
安装uview-plus,如下:

根据官方文档去配置相关依赖: uview-plus

pnpm i sass@1.63.2 -D

image.png

pnpm i sass-loader@10 -D

image.png

pnpm install uview-plus

image.png

pnpm install dayjs

image.png

pnpm install clipboard

image.png

image.png

image.png

image.png

image.png

配置easycom组件模式

image.png

引入成功,如下:

image.png

8.tabbar
 "tabBar": {
    "color": "#7A7E83",
    "selectedColor": "#313131",
    "backgroundColor": "#ffffff",
    "list": [
      {
        "pagePath": "pages/tabbar/index/index",
        "text": "首页",
        "iconPath": "static/2.png",
        "selectedIconPath": "static/1.png"
      },
      {
        "pagePath": "pages/tabbar/like/like",
        "text": "推荐",
        "iconPath": "static/3.png",
        "selectedIconPath": "static/4.png"
      },
      {
        "pagePath": "pages/tabbar/my/my",
        "text": "我的",
        "iconPath": "static/6.png",
        "selectedIconPath": "static/5.png"
      }
    ]
  }

image.png

9.封装请求

一、uniapp提供的网络请求

uniapp.dcloud.net.cn/api/request…

uni.request({
    url:"",
    data:{},
    success:res=>{
        console.log(res)
    }
})
二、将uni.request进行二次封装

1.新建一个utils/request.js文件

const BASE_URL = "";

const request = ({
  url, // 请求url
  method, // 请求方式:get/post/put/delete
  params, // get请求提交参数
  data, // post/put请求提交参数
  headers, // 请求头
}) => {
  return new Promise((resolve, reject) => {
    if (!headers) {
      const token = uni.getStorageSync("token");
      headers = {
        "Content-Type": "application/json;charset=utf-8",
        Authorization: token,
        TENANT_ID: 1,
      };
    }
    uni.request({
      url: BASE_URL + url,
      data: method === "get" ? params : data,
      method: method,
      header: headers,
      // 收到开发者服务器成功返回的回调函数
      success: (res) => {
        const { code, msg } = res.data;
        if (code == 200) {
          return resolve(res.data.data);
        }
        uni.showToast({
          icon: "none",
          duration: 3000,
          title: msg,
        });
        return reject(msg);
      },
      // 接口调用失败的回调函数
      fail(error) {
        console.log("请求错误:", error);
        uni.showToast({
          icon: "none",
          duration: 3000,
          title: "网络异常,请稍后重试!",
        });
        return reject(error);
      },
      // 接口调用结束的回调函数(调用成功、失败都会执行)
      complete() {},
    });
  });
};

export default request;

2.在src/api目录下新建一个index.js文件

// 拿到所有api
const modulesFiles = import.meta.glob('./*/*.{js,ts}', { eager: true })
const modules = {}

Object.keys(modulesFiles).forEach((key) => {
  const mod = modulesFiles[key]
  const fileNameMatch = key.match(/([^/]+)\.(js|ts)$/)
  const moduleName = fileNameMatch ? fileNameMatch[1] : 'unknown'

  modules[moduleName] = mod.default || mod
})

console.log('[API Modules]', modules)
export default modules

image.png 3.配置全局变量api

main.js

import { createSSRApp } from "vue";

import api from "@/api/index.js";

export function createApp() {
  const app = createSSRApp(App);
  //配置全局api
  app.config.globalProperties.$api = api
  return {
    app,
  };
}

4.测试 api/test/test.js

import request from '@/utils/request';

const BASE_API = '/api/test';

export default {
    time() {
        return request({
            url: BASE_API + '/time',
            method: 'get',
        });
    },
};

5.页面中来进行一个测试 pages/tabbar/index.vue

<template>
  <text>hello</text>
</template>

<script setup>
const { proxy } = getCurrentInstance();

async function test() {
  let res = await proxy.$api.test.time();
  console.log(res);
}
test();
</script>

<style lang="scss" scoped></style>

image.png

10.pinia状态管理库(作用就是存储的持续化和模块化)

1.安装依赖
# 这里不要使用最新版,uniapp可能还不兼容还是怎么的,用低版本的
cnpm i pinia@2.0.36 

image.png

2.安装pinia-plugin-persistedstate适用于Pinia的持久化存储插件
cnpm i pinia-plugin-persistedstate

image.png

3.配置
main.js
import { createSSRApp } from "vue";
import App from "./App.vue";
import uviewPlus from "uview-plus";
import api from "@/api/index.js";
import store from "@/store";
import { createPinia } from "pinia";
import { createPersistedState } from "pinia-plugin-persistedstate";
import { useTestStore } from "@/store/modules/test.js";

export function createApp() {
  const app = createSSRApp(App);
  const pinia = createPinia();
  pinia.use(
    createPersistedState({
      storage: {
        getItem(key) {
          const data = uni.getStorageSync(key);
          return data ? JSON.parse(data) : null;
        },
        setItem(key, value) {
          uni.setStorageSync(key, JSON.stringify(value));
        },
        removeItem(key) {
          uni.removeStorageSync(key);
        },
      },
      auto: true,
    })
  );
  app.use(pinia);
  //useTestStore挂载一下
  app.config.globalProperties.$store = {
    test: {
      useTestStore: useTestStore,
    },
  };
  app.use(uviewPlus);
  app.config.globalProperties.$api = api;
  return {
    app,
  };
}

image.png

模块化:

新建一个store文件,然后再store下建一个index.js
const modulesFiles = import.meta.glob("./modules/*.js");

const modules = {};

async function loadModules() {
  const moduleEntries = await Promise.all(
    Object.keys(modulesFiles).map(async (key) => {
      const moduleName = key.match(/\.\/modules\/([^.]+)\./)?.[1]; 
      const module = await modulesFiles[key]();
      return [moduleName, module.default || module];
    })
  );
  return Object.fromEntries(moduleEntries);
}

// 导出一个异步函数或用于异步初始化
export { loadModules };



再新建一个store/modules/test.js测试页面

import { defineStore } from 'pinia'  //pinia的使用
import { ref } from 'vue'

export const useTestStore = defineStore('test', () => {
  const count = ref(0)

  function add() {
    count.value++
  }
  return { count, add }
})
在页面里pages页面里用,两种响应式的写法:
响应式可以使用toRefs或者storeToRefs

pinia.vuejs.org/zh/core-con…

第一种写法

<template>
  <view>
    <view>11111111111111111111111111</view>
    <text>{{ $store.test.useTestStore().count }}</text> // 这种是响应式的
    <button @click="$store.test.useTestStore().add">click</button>
  </view>
</template>

<script setup>
const { proxy } = getCurrentInstance();
const title = ref("hello");
proxy.$store.test.useTestStore().add();
const count = proxy.$store.test.useTestStore().count;
</script>

<style lang="scss" scoped></style>

image.png

image.png

第二种写法:

<template>
  <view>
    <text>{{ count }}</text>
    <button @click="$store.test.useTestStore().add">click</button>
  </view>
</template>

<script setup>
import { toRef } from "vue";

const { proxy } = getCurrentInstance();

let { count } = toRefs(proxy.$store.test.useTestStore());
</script>

<style lang="scss" scoped></style>

image.png

持久化的本地存储

image.png

11.全局组件

一.easycom

uniapp.dcloud.net.cn/component/#…

传统vue组件,需要安装、引用、注册,三个步骤后才能使用组件。easycom将其精简为一步。

只要组件安装在项目的components目录下或uni_modules目录下,并符合components/组件名称/组件名称.vue目录结构。就可以不用引用、注册,直接在页面中使用。

<template>
    <view>
            <uni-rate></uni-rate><!-- 这里会显示一个五角星,并且点击后会自动亮星 -->
    </view>
	</template>
<script>
// 这里不用import引入,也不需要在components内注册uni-list组件。template里就可以直接用
    export default {
        data() {
            return {

            }
        }
    }
</script>

12.mixin

创建一个mixin.js 公共的实例,然后把mixin引入一下在全局main.js里面,然后页面就能直接用了,也可以局部引用。
// 抽取公用的实例 - 操作成功与失败消息提醒内容等
export default {
    data() {
        return {
            sexList: [
                { name: '不想说', value: 0 },
                { name: '男', value: 1 },
                { name: '女', value: 2 },
            ],
        };
    },
    methods: {
        // 操作成功消息提醒内容
        submitOk(msg) {
            uni.showToast({
                title: msg || '操作成功!',
            });
        },
        // 操作失败消息提醒内容
        submitFail(msg) {
            uni.showToast({
                icon: 'none',
                duration: 3000,
                title: msg || '网络异常,请稍后重试!',
            });
        },
        // 加载框
        submitLoading(msg) {
            uni.showLoading({
                title: msg || '加载中',
            });
            setTimeout(function () {
                uni.hideLoading();
            }, 1000);
        },
    },
};
全局引用
import { createSSRApp } from 'vue';
import App from './App.vue';

// 抽取公用的实例 - 操作成功与失败消息提醒内容等
import mixin from '@/utils/mixin.js';

export function createApp() {
    const app = createSSRApp(App);

    app.mixin(mixin);

    return { app };
}
局部引用
<script>
import mixin from '@/utils/mixin.js';
export default {
    mixins: [mixin],
};
</script>


<script setup>
const { proxy } = getCurrentInstance();

async function submit() { 
    proxy.submitOk('保存成功');
}
</script>

在页面中使用

<template>
    <view>{{ sexList }}</view>
</template>

<script setup>
const { proxy } = getCurrentInstance();

async function submit() { 
    proxy.submitOk('保存成功');
}
</script>

13.全局过滤器filters

当后端返回的数据格式不是前端想要的时候,我们前端可以将后端返回的数据处理成自己需要的格式。 常见的有:

    1. 时间过滤器

在utils文件中新建filters.js

import { getCurrentInstance } from "@vue/runtime-core"

export const filters ={
	//获取性别值
	sexName:(sex)=>{
		//拿到mixin混入的属性值
		const {proxy} = getCurrentInstance();
		return proxy.sexList.find((obj)=>obj.value==sex).name;
	},
};

在main.js中引入

import filters from "@/utils/filters.js";

//省略...

app.config.globalProperties.$filters=filters;

页面中 使用

{{ $filters.sexName(userObj.sex) }}