根据当前的屏幕大小/10来计算根节点的font-size
pnpm i lib-flexible # 引入样式
src/main.js
import 'lib-flexible'
把css文件中的px单位转成rem,具体转换比例看postcss.config.cjs。
pnpm i postcss-pxtorem -D
postcss.config.cjs
module.exports = {
plugins: {
'postcss-pxtorem': {
// rootvalue是根据设计稿来计算的 vant在设计的时候 设计稿用的是375px
rootValue: 37.5, // Vant 官方根字体大小是 37.5
propList: ['*'] // 支持哪些属性的转换
}
}
}
报红,配置.eslintrc.cjs。是因为默认不支持node的CommonJS规范,只支持ESModule,而CommonJS规范2023年依旧广泛使用于node环境。而我们项目中,有一些配置相关的代码是在node环境下跑的,一般我们需要让这些在node环境下跑的代码后缀写成cjs,以便区分项目中的代码是跑在ESModule还是node。
.eslintrc.cjs 配置让项目环境支持node的CommonJS规范及浏览器的ES6Module规范。
module.exports = {
env: {
browser: true,
node: true
},
}
src/App.vue 看移动端宽度为414px时,背景是否占满整个屏幕。
根组件
安装依赖
pnpm i unplugin-vue-components #组件按需导入
pnpm i vant #UI框架主体
配置插件需要的配置项
vite.config.js
//实现组件内的动态导入,将vant-ui中的组件自动注册成全局组件。
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [vue(), vueJsx(), Components({ resolvers: [VantResolver()] })]
})
测试普通组件
src/App.vue
按钮
配置全局属性或全局方法
有些组件不能直接按需导入,需要配置到全局属性或全局指令或全局方法上
src/installVant.js 配置一些不能直接按需导入的全局属性及全局方法,以插件的形式来写,让相关功能的代码更统一在一起。
import { showToast, showDialog, showNotify, showImagePreview, Lazyload } from 'vant'
import 'vant/es/toast/style'
import 'vant/es/dialog/style'
import 'vant/es/image-preview/style'
import 'vant/es/notify/style'
export function installVant(app) {
//属性
app.config.globalProperties.$showToast = showToast
app.config.globalProperties.$showDialog = showDialog
app.config.globalProperties.$showNotify = showNotify
app.config.globalProperties.$showImagePreview = showImagePreview
// 指令
app.use(Lazyload)
// ... 配置vant中的指令和属性,增加相关配置
}
src/main.js 导入上方配置好的全局属性及指令
import { installVant } from './installVant'
// 使用这行的前提是app已经创建,并且在app挂载到具体DOM前。
installVant(app)
测试是否全局属性已经按需导入了。
src/App.vue
pnpm i unplugin-auto-import
// 用于处理自动导入vue与vue-router与pinia中的方法。
import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
eslintrc: {
enabled: true // 开启后,生成eslint配置文件;
}
})
],
})
运行一次之后,会生成一个/.eslintrc-auto-import.json
文件,之后把plugins->AutoImport->eslintrc.enabled为false,防止每次都重新生成。
vite.config.js
// 用于处理自动导入vue与vue-router与pinia中的方法。
import AutoImport from 'unplugin-auto-import/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
eslintrc: {
enabled: false // 开启后,生成eslint配置文件;
}
})
],
})
用新生成文件的文件名,配置到.eslintrc.cjs中
.eslintrc.cjs
/* eslint-env node */
module.exports = {
extends: [
'.eslintrc-auto-import.json',
],
}
测试
npm run lint
;每次提交前执行npm run lint
;
使用husky插件
git init # git仓库初始化
pnpm install husky -D # 安装husky包
npm pkg set scripts.prepare="husky install" # 设置prepare命令脚本
pnpm prepare # 执行prepare命令
npx husky add .husky/pre-commit "pnpm lint" # 添加提交钩子
安装依赖
pnpm install @commitlint/cli @commitlint/config-conventional -D
添加钩子命令
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit `echo "\$1"`'
设置插件
module.exports = {
extends: ["@commitlint/config-conventional"],
};
每次提交时,要加前缀,写的注释有要求。前缀样式如下
build | 主要⽬的是修改项⽬构建系统(例如 glup,webpack,rollup 的配置等)的提交 |
---|---|
chore | 不属于以上类型的其他类型 ci 主要⽬的是修改项⽬继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle 等)的提交 |
docs | ⽂档更新 |
feat | 新功能、新特性 |
fix | 修改 bug |
perf | 更改代码,以提⾼性能 |
refactor | 代码重构(重构,在不影响代码内部⾏为、功能下的代码修改) |
revert | 恢复上⼀次提交 |
style | 不影响程序逻辑的代码修改(修改空⽩字符,格式 缩进,补全缺失的分号等,没有改变代码逻辑) |
test | 测试⽤例新增、修改 |
module.exports = {
rules: {
'vue/multi-word-component-names': 0
},
}
home
Category
Cart
User
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', redirect: '/home' },
{ path: '/home', name: 'home', component: Home },
{ path: '/category', name: 'category', component: () => import('../views/Category.vue') },
{ path: '/cart', name: 'cart', component: () => import('../views/Cart.vue') },
{ path: '/user', name: 'user', component: () => import('../views/User.vue') }
]
})
export default router
//实现组件内的动态导入,将vant-ui中的组件自动注册成全局组件。
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
Components({
dirs: ['src/components'], // 希望components下的文件夹 自动变成全局组件
resolvers: [VantResolver()]
}),
],
})
选择图标,选择Symbol,得到iconfont.js。
去除fill=""
的所有属性,去除连为默认背景色。
src/assets/iconfont.js
/* eslint-disable */
;(window._iconfont_svg_string_4175143 =
''),
(function (e) {
var t = (t = document.getElementsByTagName('script'))[t.length - 1],
l = t.getAttribute('data-injectcss'),
t = t.getAttribute('data-disable-injectsvg')
if (!t) {
var c,
a,
o,
i,
q,
n = function (t, l) {
l.parentNode.insertBefore(t, l)
}
if (l && !e.__iconfont__svg__cssinject__) {
e.__iconfont__svg__cssinject__ = !0
try {
document.write(
''
)
} catch (t) {
console && console.log(t)
}
}
;(c = function () {
var t,
l = document.createElement('div')
;(l.innerHTML = e._iconfont_svg_string_4175143),
(l = l.getElementsByTagName('svg')[0]) &&
(l.setAttribute('aria-hidden', 'true'),
(l.style.position = 'absolute'),
(l.style.width = 0),
(l.style.height = 0),
(l.style.overflow = 'hidden'),
(l = l),
(t = document.body).firstChild ? n(l, t.firstChild) : t.appendChild(l))
}),
document.addEventListener
? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState)
? setTimeout(c, 0)
: ((a = function () {
document.removeEventListener('DOMContentLoaded', a, !1), c()
}),
document.addEventListener('DOMContentLoaded', a, !1))
: document.attachEvent &&
((o = c),
(i = e.document),
(q = !1),
d(),
(i.onreadystatechange = function () {
'complete' == i.readyState && ((i.onreadystatechange = null), s())
}))
}
function s() {
q || ((q = !0), o())
}
function d() {
try {
i.documentElement.doScroll('left')
} catch (t) {
return void setTimeout(d, 50)
}
s()
}
})(window)
src/main.js中导入图标所在的js文件
src/main.js
import './assets/iconfont'
创建一个svg的通用样式组件。
src/components/SvgIcon.vue
在需要用到的地方里,使用该svg通用样式组件。
src/App.vue 注:SvgIcon组件由于在src/components中,之前已经把其配置为全局组件了。
首页
分类
购物车
我的
:root:root {
--van-primary-color: #1baeae;
}
import './assets/theme.css'
首页
分类
购物车
我的
pnpm install less
@theme: #1baeae;
export default defineConfig({
css: {
preprocessorOptions: {
less: {
additionalData: '@import "/src/assets/var.less";'
}
}
}
})
content
首页
分类
购物车
我的
新峰商城
山河无恙,人间皆安
登录
pnpm install axios
import axios from 'axios'
// 请求的时候可以做什么事? 1)拦截 携带token, 对响应状态码处理, 2)增加loading (增添一个队列) 3)接口可以保存取消的操作
class Http {
constructor() {
// 根据环境变量设置请求的路径
this.baseURL = import.meta.env.DEV ? 'http://backend-api-01.newbee.ltd/api/v1' : '/'
this.timeout = 5000
}
setInterceptor(instance) {
instance.interceptors.request.use(
(config) => {
// 携带token来做处理
return config
},
(err) => {
return Promise.reject(err)
}
)
instance.interceptors.response.use(
(res) => {
return res.data
},
(err) => {
return Promise.reject(err)
}
)
}
request(options) {
// 请求会实现拦截器
const instance = axios.create() // 1.每次请求要创建一个新的实例
this.setInterceptor(instance) // 2.设置拦截器
// 发送请求参数
return instance({
...options,
baseURL: this.baseURL,
timeout: this.timeout
})
}
get(url, data) {
return this.request({
method: 'get',
url,
params: data
})
}
post(url, data) {
return this.request({
method: 'post',
url,
data
})
}
}
export default new Http()
import http from '@/utils/http.js'
const API_LIST = {
queryIndexInfos: '/index-infos' // 首页的获取数据接口,都在这里
}
// 首页数据的获取,直接通过 api 这个文件来操作
export function queryIndexInfos() {
return http.get(API_LIST.queryIndexInfos)
}
新峰商城
山河无恙,人间皆安
登录
,之后例子设置定位。
新峰商城
山河无恙,人间皆安
登录
export default [
{
name: '新蜂超市',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E8%B6%85%E5%B8%82%402x.png',
categoryId: 100001
},
{
name: '新蜂服饰',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E6%9C%8D%E9%A5%B0%402x.png',
categoryId: 100003
},
{
name: '全球购',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E7%90%83%E8%B4%AD%402x.png',
categoryId: 100002
},
{
name: '新蜂生鲜',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%94%9F%E9%B2%9C%402x.png',
categoryId: 100004
},
{
name: '新蜂到家',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%88%B0%E5%AE%B6%402x.png',
categoryId: 100005
},
{
name: '充值缴费',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%85%E5%80%BC%402x.png',
categoryId: 100006
},
{
name: '9.9元拼',
imgUrl: 'https://s.yezgea02.com/1604041127880/9.9%402x.png',
categoryId: 100007
},
{
name: '领劵',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E9%A2%86%E5%88%B8%402x.png',
categoryId: 100008
},
{
name: '省钱',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E7%9C%81%E9%92%B1%402x.png',
categoryId: 100009
},
{
name: '全部',
imgUrl: 'https://s.yezgea02.com/1604041127880/%E5%85%A8%E9%83%A8%402x.png',
categoryId: 100010
}
]
新峰商城
山河无恙,人间皆安
登录
新品上线
{{ goods.goodsIntro }}
{{ goods.sellingPrice }}
import { createApp } from 'vue';
import { Lazyload } from 'vant';
const app = createApp();
app.use(Lazyload);
// 注册时可以配置额外的选项
app.use(Lazyload, {
lazyComponent: true,
});
{{ title }}
{{ goods.goodsIntro }}
{{ goods.sellingPrice }}
export function processURL(imgUrl) {
// 如果你是以https开头的,直接采用即可
// 否则则增加访问地址
if (!/^https?:\/\//.test(imgUrl)) {
return `http://backend-api-01.newbee.ltd${imgUrl}`
}
return imgUrl
}
export function addPrefix(money) {
// 如果你是以https开头的,直接采用即可
// 否则则增加访问地址
return '¥' + money
}
{{ title }}
{{ goods.goodsIntro }}
{{ addPrefix(goods.sellingPrice) }}
{{ title || $route.meta.title }}