vue3 + vant3.x 项目 更新vant typescript---记录总结

7 篇文章 0 订阅

下载 vant

yarn add vant

引入组件

yarn add unplugin-vue-components -D

vite.config.js

import vue from '@vitejs/plugin-vue';
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';

export default {
  plugins: [
    vue(),
    Components({
      resolvers: [VantResolver()],
    }),
  ],
};

Rem 适配

yarn add postcss-pxtorem lib-flexible

postcss.config.js

module.exports = {
  plugins: {
    // postcss-pxtorem 插件的版本需要 >= 5.0.0
    'postcss-pxtorem': {
      rootValue({ file }) {
        return file.indexOf('vant') !== -1 ? 37.5 : 75;
      },
      propList: ['*'],
    },
  },
};

注意 main.js 引入 lib-flexible

import "lib-flexible/flexible.js";

在这里插入图片描述
package.json
删除 “type”: “module”, 或者改为 “type”:“commjs”

router

yarn add vue-router@4

src/router/index.js

import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
import home from "./home";

const test = [
  {
    path: "/test",
    name: "test",
    component: () => import("@/views/test/Test.vue"),
    meta: {
      title: "测试",
      requiresAuth: false,
    },
  },
];

const notFound = [
  {
    path: "/:pathMatch(.*)*",
    name: "NotFound",
    component: () => import("@/views/error/NotFound.vue"),
  },
];

const routes = [...home, ...test, ...notFound];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

router.beforeEach(async (to, from) => {
  if (to.meta.title) document.title = to.meta.title;
  else document.title = "xx";
  return true;
});

export default router;

main.js

import router from "@/router";
createApp(App).use(pinia).use(router).mount("#app");

app.vue

<template>
  <router-view></router-view>
</template>

pinia

yarn add pinia
yarn add pinia-plugin-persist

src/store/common.js

import { defineStore, createPinia } from "pinia";

const id = "@@common";

const initialState = {
  keepAlive: [],
};

export const useCommonStore = defineStore(id, {
  state: () => ({ ...initialState }),
  getters: {},
  actions: {},
  persist: {
    enabled: true,
  },
});

export function useCommonStoreWithOut() {
  return useCommonStore(createPinia());
}

main.js

import { createPinia } from "pinia";
import piniaPersist from "pinia-plugin-persist";
const pinia = createPinia();
pinia.use(piniaPersist);
createApp(App).use(pinia).use(router).mount("#app");

axios

src/utils/http.js


import axios from "axios";
import { getLocalData, removeLocalData } from "@/utils/utils";
import { Toast } from "vant";

const noLogin = ["/login"];

const instance = axios.create({
  baseURL: import.meta.env.VITE_BASE_API,
  timeout: 50000,
  headers: { "Content-Type": "application/json" },
});

instance.interceptors.request.use(
  (config) => {
    if (!noLogin.includes(config?.url)) {
      const token = getLocalData("token");
      if (true) {
        config.headers = {
          ...config.headers,
          Authorization: `Bearer ${token}`,
        };
      }
    }

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

instance.interceptors.response.use(
  (response) => {
    console.log("response", response.data);
    if (response.data?.code !== 200) {
      if (response.data?.message) Toast(response.data?.message);
      return response.data;
    } else {
      return response.data;
    }
  },
  (error) => {
    console.log("error", error);
    if (error?.response?.data?.code == 599) {
      removeLocalData("token");
    } else {
      Toast(error?.response?.data?.message || error?.message || "服务器异常,请稍后重试");
      return new Promise(() => ({}));
    }
    // return Promise.reject(error);
  }
);

export default instance;

src/servers/common.js

import services from "@/utils/http";
export const getName = () => services.get(`/api/getName`);

上传文件


// 上传
export const upload = async (file: File) => {
	const formData = new FormData();
	formData.append("file", file);
	const res: AxiosData<UploadData> = await services.post("/api/upload/image", formData, {
		headers: { "Content-Type": "multipart/form-data" },
	});
	return res;
};

配置别名

vite.config.js

 resolve: {
      // 路径别名配置
      alias: {
        "@": path.resolve(__dirname, "./src"),
      },
    },

// vscode 路径提示
jsconfig.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "jsx": "preserve"
  },
  "exclude": ["node_modules", "dist"]
}

normalize.css

下载

yarn add normalize.css

main.js

import "normalize.css";

eslint prettier, husky lint-staged, @commitlint/config-conventional @commitlint/cli

npm init @eslint/config

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

.eslintrc.js 修改

module.exports = {
	env: {
		browser: true,
		es2021: true,
	},
	extends: [
		// "eslint:recommended",
		// "plugin:vue/vue3-essential",
		"plugin:vue/vue3-recommended",
		"plugin:@typescript-eslint/recommended",
	],
	overrides: [],
	parser: "vue-eslint-parser",
	parserOptions: {
		parser: "@typescript-eslint/parser",
		ecmaVersion: "latest",
		sourceType: "module",
	},
	plugins: ["vue", "@typescript-eslint"],
	rules: {
		// indent: 'off',
		// 'vue/script-indent': ['error', 2, { baseIndent: 1 }],
		// 'linebreak-style': ['error', 'unix'],
		// quotes: ['error', 'single'],
		// semi: ['error', 'always'],
		// 'vue/no-multiple-template-root': 'off',
		// 'vue/no-v-model-argument': 'off',
	},
};

常用命令
检测文件
npx eslint “src/App.vue”
npx eslint --ext .js,vue src
npx eslint “src/**/*.{js,vue}”

格式化文件
npx eslint “src/**/*.{js,vue}” --fix

添加 .eslintignore

/node_modules
/public
/dist
.vscode
.husky
components.d.ts
auto-imports.d.ts

Prettier

如果您使用 ESLint,请安装 eslint-config-prettier 以使 ESLint 和 Prettier 彼此配合得很好。它会关闭所有不必要的或可能与 Prettier 冲突的 ESLint 规则。
pnpm add eslint-config-prettier prettier -D

.eslintrc.js 修改

module.exports = {
	extends: [
		...
		"prettier"
	],
};

添加 .prettierrc.json

{
    "singleQuote": true,
    "printWidth": 80,
    "tabWidth": 4,
    "endOfLine": "lf",
    "semi": true,
    "trailingComma": "all",
    "vueIndentScriptAndStyle": true,
    "htmlWhitespaceSensitivity": "ignore"
}

添加 .prettierignore

/node_modules
/public
/dist
.vscode
.husky
components.d.ts
auto-imports.d.ts

格式化代码
npx prettier --write .

vscode 插件 ESLint Prettier - Code formatter

husky lint-staged

pnpm add husky lint-staged -D

npm pkg set scripts.prepare=“husky install”

package.json 生成命令

{
	"scripts": {
			...
	    "prepare": "husky install"
	}
}

npm run prepare
根目录生成 .husky 文件夹

npx husky add .husky/pre-commit “npx lint-staged”
.husky 文件夹 生成 pre-commit
pre-commit

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 提交 git 时会自动执行此命令
npx lint-staged

package.json

{
	...
	"lint-staged": {
		"*.{js,jsx,ts,tsx,vue}": [
				"eslint --cache --fix",
				"prettier --write"
		]
	}
}

@commitlint/config-conventional @commitlint/cli

下载

 pnpm add  @commitlint/config-conventional @commitlint/cli -D

添加 commitlint.config.js
注意 如果是命令创建注意修改编码为 utf-8

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

使用 Husky 的 commit-msg 钩子

npx husky install

npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'

commitlint.config.js
1:0 error Parsing error: Invalid character
echo "module.exports 编码不是utf-8
把 commitlint.config.js 编码格式改为 utf-8

CRLF 和 LF

在这里插入图片描述

在你使用git拉取代码的时候,git会自动将代码当中与你当前系统不同的换行方式转化成你当前系统的换行方式,从而造成这种冲突。

解决方案 vscode

  • 在设置里Eol 选\n

  • 修改git全局配置,禁止git自动将lf转换成crlf, 命令 然后重新克隆项目
    git config --global core.autocrlf false

自定义的全局变量没有覆盖上 vant 全局变量

:root {
  --van-primary-color: #00418e !important;
}

vueuse

useElementSize 获取不到元素的边距,可以嵌套一层给内层元素添加边距

<header ref="headerRef"></header>
<div :style="{ height: swiperHeight }"></div>
import { useElementSize } from "@vueuse/core";
const headerRef = ref(null);
const { height: headerHeight } = useElementSize(headerRef);
const swiperHeight = computed(() => {
	return `calc(100vh - ${headerHeight.value}px)`;
});

calc

calc 写法
.hot{
	width: calc((100% - 150px) / 3);
}
		
calc 写入 css 变量	
.root {
	height: calc(100vh - var(--van-nav-bar-height) - var(--van-tabbar-height));
}

calc 写入响应式变量
import { useDebounceFn, useElementSize } from "@vueuse/core";
const header = ref(null);
const { height } = useElementSize(header);
const styles = computed(() => {
	return `min-height: calc(100vh - ${height.value}px`;
});
<div :style="styles"></div>

下拉刷新、上拉加载更多

<van-tab title="我收到的" :dot="isDot">
	<div class="tabs">
	  <van-pull-refresh v-model="refreshLoading" @refresh="onRefresh">
		<van-list
		  v-model:loading="state.list[1].loading"
		  :finished="state.list[1].finished"
		  finished-text="没有更多了"
		  @load="onLoad"
		  :immediate-check="false"
		>
		</van-list>
		<template v-else><van-empty description="暂无内容" /></template>
	  </van-pull-refresh>
	</div>
</van-tab>
const active = ref(0);
const state = reactive({
  list: [
    {
      page: 1,
      limit: 10,
      data: [],
      loading: true,
      finished: false,
    },
    {
      page: 1,
      limit: 10,
      data: [],
      loading: true,
      finished: false,
    },
  ],
});
// 初始化
onMounted(() => {
  fetchData(active.value);
});
// 上拉加载
const onLoad = async () => {
  fetchData(active.value);
};
// 下拉刷新
async function onRefresh() {
  onLoad();
}
// 获取数据
async function fetchData(index) {
  try {
    let res;
	// 刷新重置数据
    if (refreshLoading.value) {
      state.list[index].page = 1;
      state.list[index].loading = true;
      state.list[index].finished = false;
    }
	// 当前列表全部加载完成退出不再请求接口
    if (state.list[index].finished) return;

    if (index == 0) {
      res = await workSchedules({
        page: state.list[index].page,
        limit: state.list[index].limit,
      });
    } else {
      res = await workReviewSchedules({
        page: state.list[index].page,
        limit: state.list[index].limit,
      });
    }
	// 全局 loading 刷新状态
    globalLoading.value = false;
    refreshLoading.value = false;

    // 加载状态结束
    state.list[index].loading = false;

    if (state.list[index].page <= 1) {
	  // 如果是第一页数据赋值
      state.list[index].data = res.data;
    } else {
	  // 否则数据合并
      state.list[index].data = [...state.list[index].data, ...res.data];
    }

    // 数据加载完成 finished 设置 true
    if (state.list[index].page >= res.meta.last_page) {
      return (state.list[index].finished = true);
    }
	// page + 1
    state.list[index].page = state.list[index].page * 1 + 1;
  } catch (error) {
    console.log(error, "error");
  }
}

van-field 默认展示一行高度自适应

rows=“1”
autosize

<van-field
	rows="1"
	type="textarea"
	maxlength="60"
	placeholder="暂未填写"
	v-model="user.serve"
	autosize
	:readonly="serveEdit"
/>

阻止默认下拉回弹效果

在这里插入图片描述

document.body.addEventListener(
	"touchmove",
	function (e) {
		e.preventDefault(); // 阻止默认的处理方式(阻止下拉滑动的效果)
	},
	{ passive: false } // passive 参数不能省略,用来兼容ios和android
);

判断手势滑动方向

在这里插入图片描述


type Direction = "left" | "right" | "up" | "down" | "initial";
// 获取方向
function getSlideDirection(startX: number, startY: number, endX: number, endY: number) {
	const dy = startY - endY;
	const dx = endX - startX;

	let direction: Direction = "initial";
	if (Math.abs(dx) < 10 && Math.abs(dy) < 10) return direction;

	const angle = getSlideAngle(dx, dy);

	if (angle >= -45 && angle < 45) direction = "right";
	else if (angle >= 45 && angle < 135) direction = "up";
	else if (angle >= -135 && angle < -45) direction = "down";
	else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) direction = "left";
	return direction;
}
// 获取角度
function getSlideAngle(dx: number, dy: number) {
	return (Math.atan2(dy, dx) * 180) / Math.PI;
}

阻止鼠标右键

// 禁止
export function prohibitCopy() {
  // 禁用右键
  document.oncontextmenu = function () {
    return false;
  };
  // 禁用选择
  document.onselectstart = function () {
    return false;
  };
}

动态加载本地资源

// 加载图片
export const getAssetsFile = (url: string) => {
	return new URL(`../assets/${url}`, import.meta.url).href;
};


<img
	:src="props.active ? getAssetsFile('tabbar/skill_select.png') : getAssetsFile('tabbar/skill.png')"
/>

一键复制粘贴板功能

// 复制
export const copyText = (str: string) => {
	//使用textarea的原因是能进行换行,input不支持换行
	var copyTextArea = document.createElement("textarea");
	//自定义复制内容拼接
	copyTextArea.value = str;
	document.body.appendChild(copyTextArea);
	copyTextArea.select();
	try {
		var copyed = document.execCommand("copy");
		if (copyed) {
			document.body.removeChild(copyTextArea);
			showToast("复制成功");
		}
	} catch {
		showToast("失败");
	}
};

picker 自定义 Columns 的结构

  <van-picker
	:columns="shiftData"
	:columns-field-names="customFieldName"
  />


const customFieldName = {
  text: "value",
  value: "key",
};

const shiftData = [
	{key: 16, value: "xx"}
]

字符串数组与数字数组快速转换

  const arr = ["1", "2", 3];
  console.log(arr.map(Number));  // [1, 2, 3]
  console.log(arr.map(String));  // ["1", "2", "3"]

$attrs 与 inheritAttrs 使用

基于 button 组件二次封装时,想要之前的属性 v-bind=“$attrs”

<template>
  <div>
    <slot>
      <van-button
        v-bind="$attrs"
        type="primary"
        size="large"
        :class="customClass"
      ></van-button
    ></slot>
  </div>
</template>

添加完之后你会发现 click 事件多次触发 inheritAttrs:false

export default {
	inheritAttrs:false
}

Tab 标签页 NavBar 导航栏 粘贴性布局

yarn add @vueuse/core
<nav-bar ref="navBarRef" />

<van-tabs
  v-model:active="active"
  color="var(--van-primary-color)"
  sticky
  :offset-top="height"
  @click-tab="handleClickTab"
>
</van-tabs>

import { useElementSize } from "@vueuse/core";
const navBarRef = ref(null);
const { height } = useElementSize(navBarRef);

Dialog 弹出框 配合 onBeforeRouteLeave 实现返回页面提示

在这里插入图片描述

注意 Dialog 要设置 closeOnPopstate: false 否则后面会出现弹框一闪

closeOnPopstate 是否在页面回退时自动关闭

<script setup>
import { onBeforeRouteLeave } from "vue-router";
import NavBar from "@/components/NavBar/index.vue";
import { Dialog } from "vant";
onBeforeRouteLeave(async () => {
  try {
    const res = await Dialog.confirm({
      message:
        "如果解决方法是丑陋的,那就肯定还有更好的解决方法,只是还没有发现而已。",
      closeOnPopstate: false,
    });
  } catch (error) {
    return false;
  }
});
</script>

如果当前页还有其他路由跳转或者确定之后跳转其他页面(出现死循环)

修改如下

onBeforeRouteLeave(async (to) => {
  try {
    if (to.path == "/mine/result") {
      return true;
    }
	// 同步方式有问题使用 .then 方式
    Dialog.confirm({
      message: "是否确认提交答案并退出考试。",
      closeOnPopstate: false,
      confirmButtonText: "提交答案",
    }).then(async () => {
      await examFinish({
        examId: route.query?.id,
      });

      router.replace({
        path: "/mine/result",
        query: {
          id: route.query?.id,
        },
      });
    });

    return false;
  } catch (error) {
    return false;
  }
});

Unhandled error during execution of render function / Unhandled error during execution of scheduler flush.

多半原因是定义的变量层级过深
<template v-if="detail.exam?.status === 1">  模板中使用 ?.

const detail = ref({});
// 推荐
const detail = ref({
  userExam: {},
  exam: {},
});

vue 结合腾讯选点组件使用

在这里插入图片描述

腾讯选点组件

调用方式二  用的是哈希路由需要 
encodeURIComponent 可以将特殊字符转义  
referer 名称
const url = encodeURIComponent(`${window.location.origin}/#/checkWork/form`);
const key = "YO7BZ-7353J";
window.location.replace(
`https://apis.map.qq.com/tools/locpicker?search=1&type=0&backurl=${url}&key=${key}&referer=myapp`
);

根据经纬度计算距离

// 经纬度计算距离 单位/米
export const GetDistance = (lat1, lng1, lat2, lng2) => {
  var radLat1 = (lat1 * Math.PI) / 180.0;
  var radLat2 = (lat2 * Math.PI) / 180.0;
  var a = radLat1 - radLat2;
  var b = (lng1 * Math.PI) / 180.0 - (lng2 * Math.PI) / 180.0;
  var s =
    2 *
    Math.asin(
      Math.sqrt(
        Math.pow(Math.sin(a / 2), 2) +
          Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)
      )
    );
  s = s * 6378.137;
  s = Math.round(s * 10000) / 10;
  return s;
};

vue 使用 js-sdk 动态引入

loadJs

function loadJs(src) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script');
    script.type = "text/javascript";
    script.src = src;
    document.body.appendChild(script);

    script.onload = () => {
      resolve();
    }
    script.onerror = () => {
      reject();
    }
  })
}

export default loadJs
// 加载微信配置
const loadWxConfig = async () => {
  try {
    if (typeof wx !== "object") {
      await loadJs("https://res.wx.qq.com/open/js/jweixin-1.6.0.js");
    }
    let wxConfig = await wxConfigApi();
    wx.config(wxConfig);
    wx.ready(() => {
      wxLocation();
    });
  } catch (error) {
    Notify(error?.message || error?.msg || "加载微信配置失败");
  }
};

使用 weixin-js-sdk

pnpm add  weixin-js-sdk

wx.d.ts
declare module "weixin-js-sdk";


import wx from "weixin-js-sdk";

wx.config(wxConfig);
wx.ready(() => {
	
})

terser not found. Since Vite v3, terser has become an optional dependency. You need to install it.

pnpm add terser

编写 svg 通用组件

下载

pnpm add vite-plugin-svg-icons -D

配置

vite.config.ts

import { createSvgIconsPlugin } from "vite-plugin-svg-icons";

plugins: [
    DefineOptions(),
    createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹(路径为存放所有svg图标的文件夹不单个svg图标)
        iconDirs: [path.resolve(process.cwd(), "src/assets")],
        // 指定symbolId格式
        symbolId: "icon-[dir]-[name]",
    }),
],

src/main.ts

import 'virtual:svg-icons-register'

编写通用组件

src/components/svgIcons/index.vue

<template>
    <svg aria-hidden="true" :style="styles">
        <use :xlink:href="iconName" :fill="color" />
    </svg>
</template>

<script setup lang="ts">
import { computed } from "vue";

const props = defineProps({
    iconClass: {
        type: String,
        required: true,
    },
    color: {
        type: String,
        default: "#08bebe",
    },
    width: {
        type: String,
        default: "20px",
    },
    height: {
        type: String,
        default: "20px",
    },
});

const styles = computed(() => {
    return `width: ${props.width}; height:${props.height}`;
});

const iconName = computed(() => {
    return `#icon-${props.iconClass}`;
});
</script>

tsconfig.json

{
  "compilerOptions": {
    "types": ["vite-plugin-svg-icons/client"]
  }
}

使用

svg存放的地址
如果是文件夹以 - 拼接
例1:src/assets/mask_star.svg
例2:src/assets/mine/mask_star.svg

引入组件

1:icon-class="mask_star"2:icon-class="mine-mask_star"
<SvgIcons width="99px" height="99px" color="#ffffff" icon-class="mine-mask_star" />

测试公众号申请

在这里插入图片描述

测试公众号申请地址

配置

配置 JS 接口安全域名
在这里插入图片描述
启动vue项目的地址
在这里插入图片描述

关注测试公众号
在这里插入图片描述

此时还需要配置 体验接口权限
在这里插入图片描述
格式和上面一致
在这里插入图片描述
准备工作已经完成

代码

const AppID = "wx0b5b0581d4b4bd63";
const redirect_uri = 'http://192.168.1.5'
// 跳转至授权页面
export const gotoWxAuth = (randStr) => {
	window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${AppID}&redirect_uri=${encodeURIComponent(
		redirect_uri
	)}&response_type=code&scope=snsapi_userinfo&state=${randStr}`;
};

测试

微信开发者工具测试
在这里插入图片描述

使用 weixin-js-sdk

下载

pnpm add  weixin-js-sdk

wx.d.ts

declare module "weixin-js-sdk";

使用

import wx from "weixin-js-sdk";
wx.config(wxConfig);
wx.ready(() => {
	...
})

自定义分享

在公众号里进入分享手机端才可以展示自定义标题

type WxData = {
	appId: string;
	debug: boolean;
	jsApiList: string[];
	nonceStr: string;
	openTagList: string[];
	signature: string;
	timestamp: number;
	url: string;
};

// 获取微信配置
export const getWechat = async (list: string[]) => {
	const res: AxiosData<WxData> = await services.get(
		"/api/wechat/config?url=" + encodeURIComponent(window.location.href),
		{ params: { list: list.join(",") } }
	);
	return res;
};
import wx from "weixin-js-sdk";
import { getWechat } from "@/services/common";

type LoadWxConfig = {
	title?: string;
	desc?: string;
	link?: string;
	imgUrl?: string;
	cb?: () => void;
};

export const loadWxConfig = async (
	list: string[] = ["updateAppMessageShareData", "updateTimelineShareData", "onMenuShareWeibo"]
) => {
	try {
		let wxConfig = await getWechat(list);
		wx.config(wxConfig.data);
	} catch (error) {
		console.log(error);
	}
};

export const customShare = async ({
	title = "设文研",
	desc = "",
	link = window.location.href,
	imgUrl = "http://swy.meikr.com/images/logo.jpg",
	cb = () => {},
}: LoadWxConfig) => {
	await loadWxConfig();
	wx.ready(() => {
		// 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
		wx.updateAppMessageShareData({
			title,
			desc,
			link,
			imgUrl,
			success: function () {
				cb?.();
			},
		});
		// 自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
		wx.updateTimelineShareData({
			title,
			link,
			imgUrl,
			success: function () {
				cb?.();
			},
		});
		// 获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口
		wx.onMenuShareWeibo({
			title,
			desc,
			link,
			imgUrl,
			success: function () {
				cb?.();
			},
			cancel: function () {},
		});
	});
};

微信公众号开放标签

<wx-open-subscribe
		template="16sQK6zFtZNB8eXMyf6yYcAmACnSpug8kM-yXQ22pjU"
		id="subscribe-btn"
		@success="onSuccess"
		@error="onError"
	>
	<div v-is="'script'" type="text/wxtag-template">
		<div
			class="btn"
			style="
				font-size: 18px;
				width: 200px;
				height: 60px;
				line-height: 60px;
				text-align: center;
				margin: 10px auto;
				background-color: aquamarine;
				color: #fff;
			"
			@click="onClick"
		>
			订阅
		</div>
	</div>
</wx-open-subscribe>

<script lang="ts" setup>
import wx from "weixin-js-sdk";

wx.config(wxConfig.data);
</script>

去除线上打印信息 console

下载

pnpm add vite-plugin-remove-console -D

vite.config.ts

import removeConsole from "vite-plugin-remove-console";

plugins: [
    removeConsole(),
],

h5 移动端调试插件 vconsole

https://github.com/Tencent/vConsole
在这里插入图片描述
下载

pnpm add vconsole -D

引入使用

import { onBeforeUnmount } from "vue";
import VConsole from "vconsole";
const vConsole = new VConsole();

console.log("Hello world");

onBeforeUnmount(() => {
	vConsole.destroy();
});

隐藏 textarea 滚动条

:deep(textarea) {
	overflow: hidden !important;
}

unplugin-auto-import 按需自动导入 API

下载

pnpm add unplugin-auto-import -D

vite.config.ts

import AutoImport from "unplugin-auto-import/vite";
plugins: [
    AutoImport({
        imports: ["vue", "vue-router"],
        dirs: ["./src/components"],
        dts: "./auto-imports.d.ts",
    }),
],

tsconfig.json

{
    "include": ["auto-imports.d.ts"]
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值