uniapp-vue3-vite 搭建小程序、H5 项目模板

1

作者GitHub:https://github.com/gitboyzcf 有兴趣可关注!!!

特色

准备

Vue3/Vite版要求 node 版本^14.18.0 || >=16.0.0

拉取默认UniApp模板

点击下载 默认模板,或者通过下面命令行拉取

npx degit dcloudio/uni-preset-vue#vite my-vue3-project

得到下面目录结构👇

1

安装依赖

我这里使用pnpm ,可以使用如npm、yarn、…

pnpm install

如有报下面错误👇

This modules directory was created using the following registries configuration:{“default”:“https://registry.npmjs.org/”}. The current configuration is {“default”:“https://registry.npm.taobao.org/”}. To recreate the modules directory using the new settings, run “pnpm install -g”.

解决方案 下载源切换

pnpm config set registry https://registry.npmjs.org

启动项目测试

执行该命令 会将此项目编译成微信小程序项目,该命令会持续监听修改并进行热更新

pnpm dev:mp-weixin

执行后会出现 dist\dev\mp-weixin文件夹结构

2

将此目录下的mp-weixin微信开发者工具进行打开

如未安装点击下面链接下载安装即可👇
https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html

结果

结果

配置自动化导入

安装依赖

pnpm i unplugin-auto-import -D

在vite.config.js中配置

配置

import AutoImport from 'unplugin-auto-import/vite'

AutoImport({
 imports: ["vue", "uni-app", "pinia"],
 dts: true,
})

配置完后 重新执行pnpm dev:mp-weixin此时会生成auto-imports.d.ts文件

此时在pages/index/index.vue中不用引入直接可以使用vue的api

<script setup>
const title = ref('Hello World!')
</script>

引入 prerttier + eslint + stylelint

安装相关依赖包👇

pnpm add -D eslint @babel/eslint-parser eslint-config-airbnb-base eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-vue vue-global-api stylelint stylelint-scss stylelint-config-standard-scss stylelint-config-prettier

.editorconfig

# .editorconfig 文件
root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off # 关闭最大行长度限制
trim_trailing_whitespace = false # 关闭末尾空格修剪

.prettierrc.cjs

module.exports = {
  // 一行的字符数,如果超过会进行换行,默认为80,官方建议设100-120其中一个数
  printWidth: 100,
  // 一个tab代表几个空格数,默认就是2
  tabWidth: 2,
  // 启用tab取代空格符缩进,默认为false
  useTabs: false,
  // 行尾是否使用分号,默认为true(添加理由:更加容易复制添加数据,不用去管理尾行)
  semi: false,
  vueIndentScriptAndStyle: true,
  // 字符串是否使用单引号,默认为false,即使用双引号,建议设true,即单引号
  singleQuote: true,
  // 给对象里的属性名是否要加上引号,默认为as-needed,即根据需要决定,如果不加引号会报错则加,否则不加
  quoteProps: 'as-needed',
  // 是否使用尾逗号,有三个可选值"<none|es5|all>"
  trailingComma: 'none',
  // 在jsx里是否使用单引号,你看着办
  jsxSingleQuote: true,
  // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
  bracketSpacing: true,
  proseWrap: 'never',
  htmlWhitespaceSensitivity: 'strict',
  endOfLine: 'auto',
}

.eslintrc.cjs

// .eslintrc.cjs 文件
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    // eslint-plugin-import 插件, @see https://www.npmjs.com/package/eslint-plugin-import
    'plugin:import/recommended',
    // eslint-config-airbnb-base 插件, tips: 本插件也可以替换成 eslint-config-standard
    'airbnb-base',
    // 1. 接入 prettier 的规则
    'prettier',
    'plugin:prettier/recommended',
    'vue-global-api',
  ],
  overrides: [
    {
      env: {
        node: true,
      },
      files: ['.eslintrc.{js}'],
      parserOptions: {
        sourceType: 'script',
      },
    },
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@babel/eslint-parser',
    sourceType: 'module',
  },
  plugins: [
    '@babel/eslint-parser',
    'vue',
    // 2. 加入 prettier 的 eslint 插件
    'prettier',
    // eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
    'import',
  ],
  rules: {
    // 3. 注意要加上这一句,开启 prettier 自动修复的功能
    'prettier/prettier': 'error',
    // turn on errors for missing imports
    'import/no-unresolved': 'off',
    // 对后缀的检测,否则 import 一个ts文件也会报错,需要手动添加'.ts', 增加了下面的配置后就不用了
    'import/extensions': [
      'error',
      'ignorePackages',
      { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' },
    ],
    // 只允许1个默认导出,关闭,否则不能随意export xxx
    'import/prefer-default-export': ['off'],
    'no-console': ['off'],
    // 'no-unused-vars': ['off'],
    // '@typescript-eslint/no-unused-vars': ['off'],
    // 解决vite.config.ts报错问题
    'import/no-extraneous-dependencies': 'off',
    'no-plusplus': 'off',
    'no-shadow': 'off',
    'vue/multi-word-component-names': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
  },
  // eslint-import-resolver-typescript 插件,@see https://www.npmjs.com/package/eslint-import-resolver-typescript
  settings: {
    'import/parsers': {
      '@typescript-eslint/parser': ['.ts', '.tsx'],
    },
    'import/resolver': {
      typescript: {},
    },
  },
  globals: {
    uni: true,
    UniApp: true,
    wx: true,
    WechatMiniprogram: true,
    getCurrentPages: true,
    UniHelper: true,
    Page: true,
    App: true,
  },
}

.stylelintrc.cjs

// .stylelintrc.cjs 文件
module.exports = {
  root: true,
  extends: [
    'stylelint-config-standard',
    'stylelint-config-standard-scss', // tips: 本插件也可以替换成 stylelint-config-recommended-scss
    'stylelint-config-recommended-vue/scss',
    'stylelint-config-html/vue',
    'stylelint-config-recess-order',
  ],
  overrides: [
    // 扫描 .vue/html 文件中的<style>标签内的样式
    {
      files: ['**/*.{vue,html}'],
      customSyntax: 'postcss-html',
    },
    {
      files: ['**/*.{css,scss}'],
      customSyntax: 'postcss-scss',
    },
  ],
  // 自定义规则
  rules: {
    // 允许 global 、export 、v-deep等伪类
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['global', 'export', 'v-deep', 'deep'],
      },
    ],
    'unit-no-unknown': [
      true,
      {
        ignoreUnits: ['rpx'],
      },
    ],
    // 处理小程序page标签不认识的问题
    'selector-type-no-unknown': [
      true,
      {
        ignoreTypes: ['page'],
      },
    ],
    'comment-empty-line-before': 'never',
  },
}

引入 husky + lint-staged + commitlint

说明
husky 用于git提交的钩子
lint-staged 一个在 git 暂存文件上(也就是被git add后的文件)运行已配置的格式工具;比如eslint、stylelintrc、…
commitlint 检查您的提交消息是否符合 常规提交格式 (Conventional commit format)
正确的提交格式:(): ,type 和 subject 默认必填
在这里插入图片描述

安装相关依赖包👇

pnpm i -D husky@6 lint-staged commitlint @commitlint/cli @commitlint/config-conventional

执行 npx husky install
并且在 package.json的scripts里面增加 "prepare": "husky install",(其他人安装后会自动执行) 根目录会生成 .hushy 文件夹。

package.josn 增加如下属性👇:

...
"scripts": {
	...
	"prepare": "husky install",
},
"lint-staged": {
  "**/*.{html,vue,ts,cjs,json,md}": [
    "prettier --write"
  ],
  "**/*.{vue,js,ts,jsx,tsx}": [
    "eslint --fix"
  ],
  "**/*.{vue,css,scss,html}": [
    "stylelint --fix"
  ]
}

根目录新增 .commitlintrc.cjs,内容如下👇

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'perf',
        'style',
        'docs',
        'test',
        'refactor',
        'build',
        'ci',
        'init',
        'chore',
        'revert',
        'wip',
        'workflow',
        'types',
        'release',
      ],
    ],
    'subject-case': [0],
  },
}

通过下面命令在钩子文件中添加内容👇

npx husky add .husky/pre-commit "npx --no-install -- lint-staged"
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

Commitizen & cz-git

说明
commitizen 基于Node.js的 git commit 命令行工具,辅助生成标准化规范化的 commit message
cz-customizable 标准输出格式的 commitizen 适配器

安装依赖包👇

pnpm add -D commitizen cz-customizable

修改 package.json指定使用的适配器

...
"scripts": {
	...
	"cz": "git-cz"
},
 "config": {
    "commitizen": {
      "path": "node_modules/cz-customizable"
    }
  }

更改提示消息模板 .cz-config.js

.cz-config.js

module.exports = {
  types: [
    { value: 'feat', name: '✨ feat:     新功能' },
    { value: 'fix', name: '🐛 fix:      修复' },
    { value: 'init', name: '🎉 Init:     初始化' },
    { value: 'docs', name: '📝 docs:     文档变更' },
    { value: 'style', name: '💄 style:    代码格式(不影响代码运行的变动)' },
    {
      value: 'refactor',
      name: '♻️  refactor: 重构(既不是增加feature,也不是修复bug)',
    },
    { value: 'perf', name: '⚡️ perf:     性能优化' },
    { value: 'test', name: '✅ test:     增加测试' },
    { value: 'revert', name: '⏪️ Revert:   回退' },
    { value: 'build', name: '🚀‍ build:    构建过程或辅助工具的变动' },
    { value: 'ci', name: '👷 ci:       CI 配置' },
  ],
  // 消息步骤
  messages: {
    type: '请选择提交类型:',
    subject: '请简要描述提交(必填):',
    customScope: '请输入修改范围(可选):',
    body: '请输入详细描述(可选):',
    breaking: '列出任何BREAKING CHANGES(可选)',
    footer: '请输入要关闭的issue(可选):',
    confirmCommit: '确认使用以上信息提交吗?',
  },
  allowBreakingChanges: ['feat', 'fix'],
  skipQuestions: ['customScope'],
  subjectLimit: 72,
}

检测

在命令行中输入👇

git add .
pnpm cz

然后会出现本次提交选项
1

选择本次提交的类型,按要求写入然后回车即可

配置UnoCss

安装依赖👇

pnpm add -D unocss @unocss/preset-uno unocss-applet @unocss/preset-legacy-compat
pnpm add @uni-helper/unocss-preset-uni

然后配置 unocss.config.js

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

import {
  presetApplet,
  presetRemRpx,
  transformerApplet,
  transformerAttributify,
} from 'unocss-applet'
import { presetUni } from '@uni-helper/unocss-preset-uni'

// @see https://unocss.dev/presets/legacy-compat
import presetLegacyCompat from '@unocss/preset-legacy-compat'

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

const presets = []
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())
}
if (!isH5) {
  presets.push(presetRemRpx())
}
export default defineConfig({
  presets: [
    presetApplet({ enable: !isH5 }),
    ...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,
    }),
  ],
  /**
   * 自定义快捷语句
   * @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)' }],
  ],
})

src/main.js增加 import 'virtual:uno.css'

vite.config.js文件写入:

import UnoCSS from 'unocss/vite'

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

检测

<view class="text-area b-1px b-solid b-color-red">
  <text class="title mt-4">{{ title }}</text>
</view>

结果👇
res

配置pinia 添加持久化

首先安装依赖包:

pnpm add pinia pinia-plugin-persistedstate -S

然后写入文件:

// src/store/count.js
import { piniaStore } from '@/store'
export const useCountStore = defineStore('count', {
  state: () => {
    return {
      count: 0
    }
  },
  actions: {
    increment() {
      this.count++
    }
  },
  persist: true // 配置持久化
})

export function useOutsideCountStore(){
  return useCountStore(piniaStore)
}

注意下面👇这个文件对持久化的处理,否则非h5环境不能正确持久化

// src/store/index.js
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

// src/main.js
import { createSSRApp } from 'vue'
import App from './App.vue'
import { setupStore } from './store'
import 'virtual:uno.css'
export function createApp() {
  const app = createSSRApp(App)
  setupStore(app)
  return {
    app
  }
}

如有报下面错误👇
vue pinia版本冲突问题

Not able to use pinia: No matching export in “node_modules/pinia/node_modules/vue-demi/lib/index.mjs” for import “hasInjectionContext”

解决方案

将vue3 和pinia更换成下面版本重新下载对应npm包
“pinia”: “2.0.32”,
“vue”: “3.2.47”,

pnpm install vue@3.2.47 pinia@2.0.32 -S

具体请看:https://github.com/vuejs/pinia/issues/2210

检测

<view>
 <button type="primary" @click="useCountStore.count++">点击 {{ count }}</button>
</view>

<script setup>
  import { useOutsideCountStore } from '@/store/count'
  const useCountStore = useOutsideCountStore()
  const count = computed(() => useCountStore.count)
</script>

res

Axios接入配置

安装相关依赖包👇

pnpm add @uni-helper/axios-adapter axios

以下步骤创建对应文件粘贴代码即可

  1. src》api》index.js

    /**
     * 模块化方式处理 默认处理 modules文件夹下的所有js文件 内容以export default导出的文件
     * @param { 模块内容集合 } moduleContext
     * @returns modules集合
     */
    const modulesHandle = (moduleContext = {}) => {
      if (!Object.keys(moduleContext).length) return
      const modules = {}
      Object.keys(moduleContext).forEach((v) => {
        for (let key in moduleContext[v].default) {
          modules[key] = moduleContext[v].default[key]
        }
      })
      return modules
    }
    
    const apis = modulesHandle(import.meta.glob('./modules/**/*.js', { eager: true }))
    export const useRequest = () => apis
    
  2. src》api》utils.js

    // 请求状态错误
    export const httpLogError = (error, msg) => {
      error.message = msg
      uni.showToast({
        title: msg,
        icon: 'error',
        duration: 2000
      })
    }
    
    // api请求错误
    export const requestError = (response) => {
      return new Promise((_, reject) => {
        const { data } = response
        const msg = `api请求出错 ${response.config.url}${data.message}`
        uni.showToast({
          title: msg,
          icon: 'error',
          duration: 2000
        })
        reject(data)
      })
    }
    
    // 登录失效
    export const throttleToLogin = () => {
      // uni.navigateTo({ url: '/pages/login/login' })
    }
    
  3. src》api》service.js

    import axios from 'axios'
    import { createUniAppAxiosAdapter } from '@uni-helper/axios-adapter'
    import { httpLogError, requestError, throttleToLogin } from './utils'
    export function createService() {
      const request = axios.create({ adapter: createUniAppAxiosAdapter() })
      request.interceptors.request.use(
        (request) => {
          return request
        },
        (err) => {
          return Promise.reject(err)
        }
      )
    
      request.interceptors.response.use(
        (response) => {
          const dataAxios = response.data
          // 这个状态码是和后端约定的
          const { code, data } = dataAxios
          console.log(dataAxios);
          // 根据 code 进行判断
          if (code === undefined) {
            return dataAxios
          } else {
            // 目前和公司后端口头约定是字符串,以防万一强制转字符串
            switch (`${code}`) {
              // code === 200 | 2 代表没有错误
              case '200':
                return data
              // code === 400001004 代表token 过期打回登录页
              case '400001004':
                throttleToLogin()
                break
              case '400':
                // 不是正确的 code
                return requestError(response)
              case '401':
                // 错误登录
                return throttleToLogin()
              default:
                // 不是正确的 code
                return requestError(response)
            }
          }
        }, 
        (error) => {
          console.log(error);
          const status = error.response?.status
          switch (status) {
            // TODO 再考虑下怎么判断是跨域问题
            case undefined:
            case null:
              httpLogError(error, '网路错误或跨域')
              break
            case 400:
              httpLogError(error, '请求错误')
              break
            case 401:
              httpLogError(error, '未授权,请登录')
              break
            case 403:
              httpLogError(error, '拒绝访问')
              break
            case 404:
              httpLogError(error, `请求地址出错: ${error.response.config.url}`)
              break
            case 408:
              httpLogError(error, '请求超时')
              break
            case 500:
              httpLogError(error, '服务器内部错误')
              break
            case 501:
              httpLogError(error, '服务未实现')
              break
            case 502:
              httpLogError(error, '网关错误')
              break
            case 503:
              httpLogError(error, '服务不可用')
              break
            case 504:
              httpLogError(error, '网关超时')
              break
            case 505:
              httpLogError(error, 'HTTP版本不受支持')
              break
            default:
              httpLogError(error, '请求错误')
              break
          }
          return Promise.reject(error)
        }
      )
      return request
    }
    
    export const service = createService()
    
  4. src》api》request.js

    import { service } from './service'
    function createRequest(service) {
      function request(config) {
      	// config 自定义配置
        // axios默认配置
        const configDefault = {
          baseURL: import.meta.env.VITE_APP_API_BASEURL, // 所有通过此配置的基础地址 在.env文件配置
          timeout: 15000, // 请求超时时间
          responseType: 'json', // 响应类型
          headers: {
            // 请求头配置...
          }
        }
        const requestConfig = Object.assign(configDefault, config)
        return service(requestConfig)
      }
      return request
    }
    
    export const request = createRequest(service)
    
  5. src》modules》demo.js

    /**
     * 命名=》API_xxx_methodName
     */
    
    import { request } from '@/api/request.js'
    export default {
      API_DEMO_POST(data = {}) {
        return request({
          url: 'demo/mock',
          method: 'post',
          data
        })
      },
    
      API_DEMO_GET(params = {}) {
        return request({
          url: '/demo/get',
          method: 'get',
          params
        })
      }
    }
    
    

在vite.config.js中配置自动化导入

在原有的配置中添加

import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
// ...
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
  	//...
    AutoImport({
      imports: [
       //...
        {
          '@/api': ['useRequest']
        }
      ],
      //...
    }),
    //...
  ],
  //...
})

检测

<template>
  <view class="content">
    <view><button type="primary" @click="getList">点击请求数据</button></view>
  </view>
  <view class="p-2">
    <view
      class="my-15rpx b-b-solid b-b-1rpx b-b-color-[#cccccc]"
      v-for="item in list"
      :key="item.id"
    >
      <text class="text-18rpx font-bold">{{ item.title }}</text>
      <view class="mt-4 text-12rpx color-[#9999]">{{ item.body }}</view>
    </view>
  </view>
</template>

<script setup>
  const { API_DEMO_GET } = useRequest()
  const list = ref([])
  
  const getList = () => {
    uni.showLoading({
      title: '加载中...'
    })
    API_DEMO_GET()
      .then((res) => {
        console.log(res)
        list.value = res
        uni.hideLoading()
      })
      .catch(() => {
        uni.hideLoading()
      })
  }
</script>

api

图标库

  1. 前往图标库查找相关图标iconify.design

  2. 安装图标所在的图标库

    # 格式:pnpm add @iconify-json/[the-collection-you-want] -D
    
    # 例如
    pnpm add @iconify-json/ep -D
    

    安装完后可以使用当前库下的所有图标
    https://icon-sets.iconify.design/ep/

  3. 项目中使用
    使用unocss的方式https://iconify.design/docs/usage/css/unocss/#icons-with-unocss

    <text class="i-ep-apple w-40rpx h-40rpx color-red"></text>
    

UI 方案

自行使用相关ui接入项目
uni-ui
https://www.uvui.cn/
https://uviewui.com/

模板

拉取后 开箱即用
模板地址👉 https://github.com/gitboyzcf/uni-preset-vue3-vite

敬请期待

后续还会有新功能接入当前模板,敬请期待…






到这里就结束了,后续还会更新 前端 系列相关,还请持续关注!
感谢阅读,若有错误可以在下方评论区留言哦!!!

111


推荐文章👇

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值