阅读视图

发现新文章,点击刷新页面。

速通-微信小程序 2Day

速通-微信小程序 2Day

速通-微信小程序-CSDN博客 紧接前文,搭配有助于快速,巩固/学习!话不多说开始!

这一部分挺简单的,最起码对于做过前端Vue 开发,前后端的 me,so so easy!

WXML 模板语法

WXML(WeiXin Markup Language)是小程序的视图层模板语言

作用类似 HTML,但扩展了「数据绑定、逻辑渲染、事件绑定」等核心能力,

是小程序 数据驱动视图 核心能力:数据绑定、逻辑渲染(列表/条件)、事件绑定;

数据绑定:

Mustache 语法 ,把data中的数据绑定到页面中渲染:

WXML 通过双大括号 {{}}将 JS 逻辑层数据渲染到视图层,支持 内容绑定、属性绑定、表达式运算

  • {{}}内支持简单表达式(算术、三元、逻辑运算),但不支持复杂语句(如if/for循环)

  • 文本绑定中,{{}}会自动解析 \n 为换行符(对应之前text组件的换行场景)

  • ⚠️绑定的数据必须在页面 data 中定义,否则会显示为空;

内容绑定: <标签> {{ 绑定页面data中的变量,文本渲染 }} </标签>

属性绑定: 标签,属性值也用 {{}} 绑定,注意,属性名无需加引号(区别于 VUE 的v-bind

xxx.js

Page({
/** 页面的初始数据 */
    data: {
img: "https://i1.hdslb.com/bfs/face/6ac26f27a6d25da7865cab8e0806676c8f4cd62c.webp",
        username: "wsm",
        age: 25
    },
    /** 省略其他配置.... */
})

xxx.wxml

<view>
  <!-- 绑定页面data中的变量 -->
  <text>用户名:{{username +'\n'}}</text>
  <text>年龄:{{age + 1 +'\n'}}</text> <!-- 支持表达式运算 -->
  <text>成年:{{age >= 18 ? '是' : '否'}} \n</text> <!-- 三元运算 -->

  <!-- 动态绑定标签属性-data中的变量 -->
  <image src="{{img}}"  mode="widthFix"></image>
</view>

在这里插入图片描述

事件绑定:

小程序的事件绑定是视图层(WXML)与逻辑层(JS)通信的核心方式 也是实现用户交互的唯一入口;

由于小程序是双线程模型(渲染层 + 逻辑层),事件需通过微信客户端(Native)中转,

因此绑定规则、事件对象与普通网页的 DOM 事件有本质差异;

在这里插入图片描述

事件 中转通信

小程序的事件并非直接的 DOM 事件,而是通过 微信客户端作为中转桥接层

将视图层用户交互 点击、输入 转发到逻辑层 JS 函数处理,这种设计避免了直接 DOM 操作;

  • 核心限制 :禁止直接操作 DOM,所有交互必须通过 事件绑定 + 数据驱动 实现

  • 触发流程 :用户在视图层触发事件 → 微信客户端捕获事件并封装成事件对象

    → 转发到逻辑层对应的 JS 函数 → 逻辑层处理后通过 setData 更新视图;

事件绑定的两种核心方式:

小程序提供 bindcatch 两种绑定前缀,核心区别是是否允许事件冒泡

绑定方式 行为 适用场景
bind 绑定事件并允许冒泡(事件会向上传递给父元素) 需要父元素响应子元素事件的场景
如:事件委托)
catch 绑定事件并阻止冒泡(事件不会向上传递) 仅需当前元素响应事件的场景
如:按钮点击、表单提交)
<!-- 父元素绑定bindtap,子元素绑定bindtap -->
<view bindtap="parentTap" style="padding: 20rpx; background: #f0f0f0;">
    <button bindtap="childTap">点击子元素(允许冒泡)</button>
</view>

<!-- 父元素绑定bindtap,子元素绑定catchtap -->
<view bindtap="parentTap" 
style="padding: 20rpx; background: #f0f0f0; margin-top: 20rpx;">
    <button catchtap="childTap">点击子元素(阻止冒泡)</button>
</view>
/** 页面定义函数 */
parentTap() {
    console.log("父元素事件触发");
},
childTap() {
console.log("子元素事件触发");
}

在这里插入图片描述

  • 点击第一个按钮(bindtap):会先触发childTap,再触发parentTap(事件冒泡到父元素)

  • 点击第二个按钮(catchtap):仅触发childTap,不会触发parentTap(阻止冒泡)

事件对象(e):交互数据的 “载体”

事件触发时,逻辑层的 JS 函数会收到一个 事件对象e,包含事件的所有信息,核心属性如下:

属性 作用
e.type 事件类型(如 tapinput
区分不同事件类型,如同时绑定多种事件时判断触发源)
e.target 触发事件的源元素(实际点击的元素)
事件委托场景中,获取触发事件的具体子元素
e.currentTarget 绑定事件的当前元素(事件绑定的元素)
获取绑定事件的元素的自定义属性或统一处理父元素逻辑
e.detail 事件携带的额外信息(如表单输入值、滑动距离)
获取表单组件的输入内容(如 input 实时值)、滑动组件的位移量
e.dataset 元素的自定义属性(通过 data-* 定义)
事件传参的核心方式(如传递商品 ID、列表项索引)
<view bindtap="handleTap" data-id="parent">
  <button data-id="child">点击按钮</button>
</view>
handleTap(e) {
  console.log(e.target.dataset.id); // 输出:child(实际触发的元素是按钮)
  console.log(e.currentTarget.dataset.id); // 输出:parent(绑定事件的元素是view)
}

target 和 currentTarget 的区别

target:指向实际触发事件的元素(子元素)

currentTarget:指向绑定事件的元素(父元素)

在这里插入图片描述

小程序 常用事件

冒泡事件(支持bind/catch

事件名 触发场景 实战用途
tap 点击元素 (手指触摸后马上离开) 按钮点击、列表项跳转、提交操作
longpress 长按元素(触摸时间≥350ms) 弹出操作菜单、删除确认
touchstart 手指触摸开始 滑动交互、手势识别
touchend 手指触摸结束 滑动结束、手势确认
touchmove 手指触摸后移动 滑动拖拽、进度条控制

非冒泡事件(仅支持bindcatch无效)

事件名 触发场景 实战用途
submit 表单提交 提交表单数据到后端
scroll 页面 / 滚动容器滚动 监听滚动位置、实现上拉加载更多
change 表单组件值变化(如 switch、picker) 监听开关状态、选择器变化
input 输入框内容变化 实时获取用户输入(如搜索框、表单)

事件传参:data-* 自定义属性

小程序不支持直接在事件绑定中传参 如: bindtap="handleClick(123)"会报错

必须通过data-*自定义属性传递参数,再从事件对象的 e.currentTarget.dataset 中获取;

<!-- 事件传参: -->
<view>
<!-- 可以为组件提供data-*自定义属性传参,其中*代表的是参数的名字,示例代码如下: -->
    <button bindtap="handleTapparam" data-param1="123" data-param2="{{123}}" >事件传参 param1</button>
</view>
handleTapparam(e){
    //通过 event.target.dataset.参数名 即可获取到具体参数的值
    //通过dataset可以访问到具体参数的值
    console.log(e.target.dataset);            
    console.log(e.target.dataset.param1);     
    console.log(e.target.dataset.param2);
}

在这里插入图片描述表单事件的detail属性

实现文本框和 data 之间的数据同步

小程序文本框(input组件)与页面data数据双向同步

核心依托小程序 数据绑定 +input 组件 输入事件监听 🖥️🖥️🖥️

通过setData完成视图层(文本框)与逻辑层(data)的双向数据更新;

  • 文本框通过value="{{data中的变量}}"实现 data 到文本框的单向渲染(初始值回显);
  • 绑定input组件的输入事件(bindinput/bindblur),实时 / 按需获取文本框输入值;
  • 在事件处理函数中,通过this.setData({ 变量: 输入值 })完成 文本框到 data 的反向同步
  • 全程遵循小程序「数据驱动视图」原则, 禁止直接操作 DOM ,所有数据更新均通过setData实现;
  <input value="{{inpmas}}" bindinput="inpvalue"
  style="border: 1px solid black; margin: 5px; padding: 5px;" />
/** 页面的初始数据 */
data: {
    img: ".................",
inpmas: '兴趣爱好',
    username: "wsm",
age: 25,
},
//data 之间的数据同步
inpvalue(e){
//通过 e.detail.value 获取到文本框最新的值
this.setData({ inpmas: e.detail.value });
console.log(this.data.inpmas);
},

在这里插入图片描述

条件渲染:

WXML 的条件渲染是小程序根据逻辑层(JS)数据动态控制视图层元素显示 / 隐藏的核心能力,

完全遵循「数据驱动视图」原则,禁止直接操作 DOM 修改显示状态

wx:if

wx:if / wx:elif / wx:else(多分支条件,逻辑销毁 / 重建)

wx:if 是 WXML 原生支持的 多分支条件渲染语法 ,支持单条件、多条件分支、嵌套判断

底层通过 销毁 / 重建组件节点 实现显示 / 隐藏(条件不满足时节点从渲染树移除,满足时重新创建)

<view>
  <!-- 当isShow为true时显示,否则销毁该节点 -->
  <view wx:if="{{isShow}}">仅满足条件时显示</view>

  <!-- 多分支按顺序匹配,仅执行第一个满足条件的分支 -->
  <view wx:if="{{status === 0}}">待支付</view>
  <view wx:elif="{{status === 1}}">已支付/待发货</view>
  <view wx:elif="{{status === 2}}">已发货/待收货</view>
  <view wx:else>已完成/已取消</view>

  <!-- block包裹多节点,一次控制所有元素的显示/隐藏 -->
  <block wx:if="{{hasData}}">
    <view>商品名称:{{goods.name}}</view>
    <view>商品价格:{{goods.price}}元</view>
    <button>立即购买</button>
  </block>
  <!-- 空状态提示 -->
  <view wx:else>暂无商品数据</view>
</view>
Page({
  /** 页面的初始数据 */
  data: {
    status: 3,
    isShow: false,
    hasData: false,
    goods: { name:"AAA", price:12.00 },
  },
})

在这里插入图片描述

<block> 包裹多节点(不生成冗余 DOM)

若需要 同时控制多个元素的条件显示,直接给每个元素加wx:if会冗余,

可使用<block>标签包裹 ——<block>是 WXML 的无渲染容器

仅用于包裹节点,不生成实际 DOM 元素,不影响页面布局;

wx:hidden

hidden 是 WXML 元素的通用属性,仅支持单分支条件判断(隐藏 / 显示)

底层通过CSS 的 display: none 实现隐藏 —— 元素节点始终存在于渲染树中

仅通过样式控制不可见,适合 单条件、高频切换 的场景(如弹窗、下拉菜单、开关控制的内容

<view>
  <!-- 方式1:直接写布尔值(静态,无动态切换需求) -->
  <view hidden="{{true}}">静态隐藏的内容</view>
  <!-- 方式2:绑定data中的布尔变量(推荐,支持动态切换) -->
  <view hidden="{{isHidden}}">高频切换的内容(如弹窗)</view>
  <!-- 方式3:结合简单表达式(无需额外定义data变量) -->
  <view hidden="{{list.length === 0}}">列表有数据时显示</view>
</view>
Page({
  /** 页面的初始数据 */
  data: {
    // 控制hidden的核心变量
    isHidden: false, 
    list: [1,2]
  },
})

在这里插入图片描述

if 🆚 hidden

两者的核心差异在于 底层实现方式,直接决定了 切换性能开销适用场景

运行方式不同:

  • wx:if 以动态创建和移除元素的方式,控制元素的展示与隐藏
  • hidden 以切换样式的方式(display: none/block;),控制元素的显示与隐藏

使用建议: 频繁切换时,建议使用 hidden,控制条件复杂时,建议使用 wx:if、wx:elif、wx:else

wx:for 列表渲染

wx:for 是 WXML 原生核心的列表渲染指令,用于将数组 / 对象中的数据循环渲染为页面节点;

将逻辑层(JS)data中的数组 / 对象,在视图层(WXML)中循环生成相同结构的节点,

实现数据与视图的联动渲染,无需手动操作 DOM;

直接给元素绑定wx:for="{{数组名}}",即可循环渲染该元素;

小程序自动提供 2 个默认变量,无需手动定义:

  • item:当前循环的 数组项 每一个元素
  • index:当前循环的 索引 从 0 开始

wx:key是必填项 无合法wx:key会触发控制台性能警告,且列表重排时易出现渲染错误

<view>
  <!-- wx:for绑定数组,wx:key绑定唯一标识 -->
  <view wx:for="{{goodsList}}" wx:key="id" class="goods-item">
    <text>第{{index+1}}个商品:</text>
    <text>名称:{{item.name}},价格:{{item.price}}元</text>
  </view>
</view>
Page({
  /** 页面的初始数据 */
  data: {
    // 模拟后端返回的商品列表,id为唯一标识
    goodsList: [
      { id: 1, name: "小程序开发实战", price: 59 },
      { id: 2, name: "Java后端进阶", price: 79 },
      { id: 3, name: "全栈开发指南", price: 99 }
    ]
  },
})

在这里插入图片描述

WXSS 模板样式

WXSS(WeiXin Style Sheet)是小程序的专属样式语言,

类似于CSS样式,并,在 CSS 基础上扩展了 rpx 响应式尺寸单位 解决移动端多设备适配复用问题;

rpx 尺寸单位

什么是 rpx 尺寸单位

rpx(responsive pixel)是小程序独有的响应式尺寸单位

用于统一不同屏幕宽度设备的尺寸显示,替代 CSS 中的固定单位 px,无需手动计算适配比例;

实现原理: 小程序将所有设备的屏幕宽度统一映射为 750rpx 750rpx = 设备实际屏幕宽度

框架会根据设备屏幕宽度自动换算 rpx 对应的物理像素:

  • 在 iPhone X(屏幕宽度 375px)上:换算比例与 iPhone 6 一致
  • 在 iPhone 6(屏幕宽度 375px)上:750rpx = 375px1rpx = 0.5px
  • 在安卓设备(如屏幕宽度 414px)上:750rpx = 414px1rpx ≈ 0.552px

设计稿适配:主流设计稿宽度为 750px,设计稿上的 1px 可直接对应小程序的 1rpx

官方建议:开发微信小程序时,设计师可以用 iPhone6 作为视觉稿的标准

@import 样式导入

用于导入外部 WXSS 样式文件,实现 公共样式复用

如:全局主题、组件样式、工具类),避免重复编写样式,提升代码可维护性;

支持 相对路径 和 绝对路径 ,必须用双引号包裹路径,结尾加分号,可导入多个文件,按顺序合并样式;

在这里插入图片描述

app.json 全局配置

小程序根目录下的 app.json 文件是小程序的 全局配置文件 常用的配置项如下:

  • pages: 记录当前小程序所有页面的存放路径,必选,小程序启动的基础
  • tabBar: 设置小程序底部的 tabBar 效果
  • window: 全局设置小程序窗口的外观
  • style: 是否启用新版的组件样式

配置 window

属性名 默认值 说明
navigationBarTitleText 字符串 导航栏标题文字内容
navigationBarBackgroundColor #000000 导航栏背景颜色,如 #000000
navigationBarTextStyle white 导航栏标题颜色,仅支持 black/white
backgroundColor #ffffff 窗口的背景色
backgroundTextStyle dark 下拉 loading 的样式,仅支持 dark/light
enablePullDownRefresh false 是否全局开启下拉刷新
onReachBottomDistance 50 页面上拉触底事件触发,距页面底部距离,单位为px
navigationStyle default custom 为自定义导航栏,适合沉浸式页面

配置 tabBar

tabBar 是小程序的 全局底部导航组件,用于在多个核心页面(如首页、我的、订单)之间快速切换;

是多页面小程序的标配。它完全在 app.json 中配置,无需编写额外代码,以下完整配置指南🧭:

小程序中通常将其分为: 底部 tabBar顶部 tabBar

  • tabBar中只能:配置最少 2 个、最多 5 个 tab 页签

  • 渲染 顶部tabBar 时,不显示 icon,只显示文本

tabBarapp.json 的顶级配置项,分为必填项和可选项

配置项 必填 / 取值 / 说明
color 是,tab 未选中时的文字颜色(十六进制色值,如 #666666
selectedColor 是,tab 选中时的文字颜色(需与 color 区分,如品牌色 #ff4444
backgroundColor 是,tabBar 的背景色(十六进制色值,如 #ffffff
borderStyle 否,tabBar 上边框的样式,仅支持 black/white,默认 black
position 否,tabBar 的位置,默认 bottom(底部),可选 top(顶部)
⚠️ 注意:position: "top" 时,不显示 icon,仅显示文字
custom 否,是否使用自定义 tabBar(默认 false
开启后需在根目录创建 custom-tab-bar 自定义组件,适合复杂交互;
list 是,tab 项的数组,最少 2 个,最多 5 个,每个元素为一个 tab 配置对象;

每个 tab 项的配置(list 数组元素)

配置项 必填 / 取值 / 说明
pagePath 是,点击 tab 跳转的页面路径,
必须是 pages 数组中已注册的页面pages/index/index
text 是,tab 上显示的文字,如 首页 我的
iconPath 否,tab 未选中时的图标路径,建议用 81px:81px 的 2x 图,避免模糊
selectedIconPath 否,tab 选中时的图标路径,需与 iconPath 尺寸一致

以下是 <常见小程序> 的典型 tabBar 配置: 首页 / 消息 / 联系我们

{
  "pages": [
    "pages/home/home",
    "pages/contact/contact",
    "pages/message/message"
  ],
  "window": {
    "navigationBarTextStyle": "black",
    "navigationBarTitleText": "我的小程序",
    "navigationBarBackgroundColor": "#FFFFFF"
  },
  "style": "v2",
  "lazyCodeLoading": "requiredComponents",
  "componentFramework": "glass-easel",
  "sitemapLocation": "sitemap.json",
  
  "tabBar": {
    "color": "#666666",
    "selectedColor": "#ff4444",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "list": [
      {
        "text": "首页",
        "pagePath": "pages/home/home",
        "iconPath": "images/tab/home.png",
        "selectedIconPath": "images/tab/home-active.png"
      },
      {
        "text": "消息",
        "pagePath": "pages/message/message",
        "iconPath": "images/tab/message.png",
        "selectedIconPath": "images/tab/message-active.png"
      },
      {
        "text": "联系我们",
        "pagePath": "pages/contact/contact",
        "iconPath": "images/tab/contact.png",
        "selectedIconPath": "images/tab/contact-active.png"
      }
    ]
  }
}
  • ⚠️ tabBar —— list 最少两个最多5个,且必须在 Page中存在!!!

在这里插入图片描述

xxx.json 局部配置

小程序中,每个页面都有自己的 .json 配置文件,用来对当前页面的窗口外观、页面效果等进行配置

小程序中,app.json 中的 window 节点,可以全局配置小程序中每个页面的窗口表现;

  • 如果,某些小程序页面想要拥有特殊的窗口表现,

  • 此时,页面级别的 .json 就可以实现这种需求

根据就近原则,最终的效果以页面配置为准

网络数据请求

小程序中网络数据请求的限制:

为保障用户数据安全,小程序的网络请求有以下强制规则:

域名白名单

  • 上线请求的域名必须在,微信公众平台 开发管理→开发设置→服务器域名

    中配置白名单,仅支持 HTTPS 协议,域名必须经过 ICP 备案,可在小程序:项目配置中查看!

    在这里插入图片描述

  • 开发阶段可在开发者工具中开启 不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书

    豁免;上线前必须完成域名备案并配置白名单; 详情——本地配置——不校验合法域名

    在这里插入图片描述

    仅在开发阶段使用,上线必须合法域名HTTPS!!

协议要求

  • 上线: 仅支持 HTTPS/WSS 协议,禁止使用 HTTP
  • 开发阶段: 可暂时豁免,上线前必须切换为 HTTPS 域名;

请求频率

  • 单个小程序的并发请求数限制为 10 个,高频请求需做节流/合并
  • 避免短时间内发起大量请求,可通过防抖 / 合并请求优化

跨域限制

  • 仅需配置微信域名白名单,无需后端设置 CORS

  • 无需配置 CORS(由微信客户端中转请求,无浏览器同源限制)

相关测试接口:

GET: https://applet-base-api-t.itheima.net/slides 获取轮播图信息;

POST: https://applet-base-api-t.itheima.net/api/post 上传用户信息;

在这里插入图片描述

发生 GET 请求

home.wxml

<!-- 轮播图容器:适配小程序轮播组件 -->
<swiper  indicator-dots  autoplay interval="3000"  
circular  indicator-active-color="#ff4444" class="banner-swiper">
  <!-- 循环渲染轮播图项 -->
  <swiper-item wx:for="{{slides}}" wx:key="id">
    <image  src="{{item.image}}"  style="height: 100%; width: 100%;"  />
  </swiper-item>
</swiper>

home.js

Page({
  /*** 页面的初始数据 */
  data: {
    slides: [] // 轮播图数据列表,初始为空
  },
  /** 生命周期函数--监听页面加载 */
  onLoad(options) {
    // 调用GET请求渲染页面轮播图~
    this.getSlides();
  },
  //GET请求获取页面轮播图信息
  getSlides() {
    // 显示加载中提示,提升用户体验
    wx.showLoading({title: '加载中...', mask: true});
    wx.request({
      url: 'https://applet-base-api-t.itheima.net/slides',
      method: 'GET',
      success: (res) => {
        // 接口请求成功:判断状态码,更新数据
        if (res.statusCode === 200 && res.data) {
          this.setData({slides: res.data });
        }else{
           // 接口返回异常,提示用户
           wx.showToast({ title: '轮播图数据加载失败', icon: 'none',duration: 2000 })
        }
      },
      fail: (err) => {
        console.error('轮播图请求失败:', err);
        wx.showToast({ title: '轮播图数据加载失败', icon: 'none',duration: 2000 })
      },
      // 无论成功/失败,都关闭加载提示
      complete: () => {
        wx.hideLoading();
      }
    })
  }
})

在这里插入图片描述

发送 POST 请求

<!-- 分割线 -->
<view class="divider">\n</view>

<!-- 用户信息输入区域(新增) -->
<view>
  <view>
    <text>姓名:</text>
    <input type="text" placeholder="请输入姓名" 
value="{{name}}" bindinput="syncInput" data-key="name"/>
  </view>
  <view class="form-item">
    <text class="label">性别:</text>
    <input type="text" placeholder="请输入性别 男/女" 
value="{{gender}}" bindinput="syncInput" data-key="gender" />
  </view>
  <!-- 提交按钮 -->
  <button bindtap="postUserInfo" class="submit-btn">提交用户信息</button>
</view>

<!-- POST请求结果展示区域(新增) -->
<view wx:if="{{postResult}}">
  <text>提交结果:</text>
  <text>{{postResult}}</text>
</view>
Page({
  /*** 页面的初始数据 */
  data: {
    slides: [], // 轮播图数据列表,初始为空
    name: '',
    gender: '',
    postResult: ''
  },
  /** 生命周期函数--监听页面加载 */
  onLoad(options) {   },
  //GET请求页面轮播图信息
  getSlides() {  },

  // 新增:同步输入框数据到data(实时绑定)
  // 通过data-key区分是name还是gender输入框
  syncInput(e) {
    const key = e.currentTarget.dataset.key;
    this.setData({[key]: e.detail.value });
  },
  // 新增:POST请求提交用户信息
  postUserInfo() {
    // 非空校验
    const { name, gender } = this.data;
    if (!name || !gender) {
      wx.showToast({ title: '请完善姓名和性别', icon: 'none' });
      return;
    }
    // 显示加载提示
    wx.showLoading({ title: '提交中...', mask: true });
    // 发起POST请求
    wx.request({
      url: 'https://applet-base-api-t.itheima.net/api/post',
      method: 'POST',
      data: { name, gender },
      header: { 'content-type': 'application/json' },
      success: (res) => {
        // 将返回的JSON转为字符串,方便页面展示
        if (res.statusCode === 200 && res.data) {
          this.setData({postResult: JSON.stringify(res.data, null, 2)})
          wx.showToast({ title: '提交成功', icon: 'success' });
        }else {
          wx.showToast({ title: '提交失败', icon: 'none' });
        }
      },
      fail: (err) => {
        console.error('POST请求失败:', err);
        wx.showToast({ title: '网络异常,请稍后重试', icon: 'none' });
      },
      complete: () => { wx.hideLoading(); },
    })
  }
})

在这里插入图片描述

小程序网络请求进阶

小程序的网络请求能力远不止 GET/POST

完全支持 RESTful 风格的 PUT/DELETE 等方法,同时提供了专门的 API 处理表单和文件传输;

相关文档:

黑马—小程序简介_哔哩哔哩_bilibili 对应:Day2 天内容!

  • blog 中涉及接口案例,可能会失效,可以到官方评论区,会定期更新最新域名!

Veaury:让Vue和React组件在同一应用中共存的神器

前端开发者常常面临这样的困境:Vue项目需要使用React生态的优秀组件,或者React项目想引入Vue的优雅解决方案。过去,这几乎意味着需要完全重写或寻找笨重的替代方案。

今天介绍的Veaury将彻底改变这一局面。这是一个专门设计用于在Vue和React之间实现无缝互操作的工具库。

核心问题与挑战

在实际开发中,跨框架组件复用面临诸多挑战:

  1. 上下文隔离:Vue和React有各自独立的上下文系统,数据传递困难
  2. 生命周期不匹配:两个框架的生命周期模型完全不同
  3. 事件系统差异:Vue使用自定义事件,React使用合成事件
  4. 渲染机制不同:Vue基于模板,React基于JSX

Veaury的技术实现原理

Veaury通过高阶组件(HOC)的方式,在两种框架之间搭建桥梁。其核心思路是:

// 简化版实现原理示意
function createCrossFrameworkWrapper(OriginalComponent, targetFramework) {
  return function Wrapper(props, context) {
    // 处理props转换
    const convertedProps = convertProps(props, targetFramework);
    
    // 处理上下文传递
    const frameworkContext = adaptContext(context, targetFramework);
    
    // 根据目标框架选择渲染方式
    if (targetFramework === 'vue') {
      return renderAsVue(OriginalComponent, convertedProps, frameworkContext);
    } else {
      return renderAsReact(OriginalComponent, convertedProps, frameworkContext);
    }
  };
}

主要特性

1. 完整的Vue 3支持

  • 支持Composition API和Options API
  • 支持Teleport、Suspense等Vue 3特性
  • 完整的响应式系统集成

2. 双向上下文共享

// React组件可以访问Vue的provide/inject
// Vue组件可以访问React的Context
const SharedComponent = ({ theme }) => {
  // theme可以来自Vue的provide或React的Context
  return <div className={`theme-${theme}`}>共享主题</div>;
};

3. 纯模式(Pure Mode)

消除包装器带来的额外DOM元素,保持组件树的整洁:

// 使用纯模式包装
const PureReactComponent = applyPureReactInVue(ReactComponent);
// 渲染结果没有额外的div包裹

4. 生命周期映射

Veaury智能地映射两个框架的生命周期:

Vue 生命周期 React 等效
onMounted useEffect(() => {}, [])
onUpdated useEffect(() => {})
onUnmounted useEffect(() => () => {})

实际应用示例

场景一:在Vue项目中使用React组件

<template>
  <div>
    <h2>Vue组件主体</h2>
    <!-- 直接使用React组件 -->
    <ReactDataTable :data="tableData" @row-click="handleRowClick" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { applyPureReactInVue } from 'veaury';
import ReactDataTable from './ReactDataTable.jsx';

// 将React组件转换为Vue可用的组件
const ReactDataTable = applyPureReactInVue(ReactDataTable);

const tableData = ref([
  { id: 1, name: '项目A', value: 100 },
  { id: 2, name: '项目B', value: 200 }
]);

const handleRowClick = (rowData) => {
  console.log('行点击事件:', rowData);
  // 处理来自React组件的事件
};
</script>

场景二:在React项目中使用Vue组件

import React, { useState } from 'react';
import { applyVueInReact } from 'veaury';
import VueRichEditor from './VueRichEditor.vue';

const RichEditor = applyVueInReact(VueRichEditor);

function App() {
  const [content, setContent] = useState('');
  const [isDarkMode, setIsDarkMode] = useState(false);

  const handleContentChange = (newContent) => {
    setContent(newContent);
    // 处理来自Vue组件的事件
  };

  return (
    <div className={isDarkMode ? 'dark-theme' : 'light-theme'}>
      <h1>React应用中的Vue富文本编辑器</h1>
      <RichEditor
        modelValue={content}
        onUpdate:modelValue={handleContentChange}
        darkMode={isDarkMode}
        v-slots={{
          toolbar: () => <div>自定义工具栏</div>
        }}
      />
      <button onClick={() => setIsDarkMode(!isDarkMode)}>
        切换主题
      </button>
    </div>
  );
}

性能考虑

Veaury在性能方面做了大量优化:

  1. 最小化重渲染:通过精细的响应式侦听,避免不必要的重新渲染
  2. 内存效率:合理管理组件实例,避免内存泄漏
  3. 构建优化:支持Tree-shaking,只引入需要的功能

性能对比示例:

// 传统iframe方案 vs Veaury方案
// iframe:独立的DOM、样式和上下文,开销大
// Veaury:共享同一DOM,轻量级包装,性能接近原生

企业级应用实践

案例:低代码平台集成

某低代码平台使用Veaury实现插件系统:

  • 核心框架:Vue 3 + TypeScript
  • 插件生态:支持React和Vue两种插件
  • 实现效果:开发者可使用任意框架开发插件

案例:微前端架构

在微前端场景中,Veaury帮助不同技术栈的子应用共享组件:

// 主应用(Vue)使用子应用(React)的组件库
import { applyPureReactInVue } from 'veaury';
import ReactDesignSystem from 'team-react-ds';

// 在Vue主应用中直接使用React设计系统
const VueWrappedButton = applyPureReactInVue(ReactDesignSystem.Button);
const VueWrappedModal = applyPureReactInVue(ReactDesignSystem.Modal);

配置与构建

Vite配置示例

// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import veauryVitePlugins from 'veaury/vite';

export default defineConfig({
  plugins: [
    veauryVitePlugins({
      type: 'vue', // 或 'react',根据主框架选择
      vueOptions: {
        reactivityTransform: true // 启用响应式语法糖
      }
    })
  ],
  optimizeDeps: {
    include: ['veaury']
  }
});

Webpack配置要点

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        use: 'vue-loader'
      },
      {
        test: /\.jsx$/,
        use: 'babel-loader',
        options: {
          presets: ['@babel/preset-react']
        }
      }
    ]
  }
};

局限性说明

尽管Veaury功能强大,但仍有一些限制:

  1. 部分高级特性:某些框架特定的高级特性可能不完全支持
  2. 开发体验:调试时需要了解两种框架
  3. 学习成本:团队需要同时熟悉Vue和React

总结

对于需要在Vue和React之间搭建桥梁的项目,Veaury提供了一个成熟、稳定的解决方案。无论是新项目技术选型,还是老项目现代化改造,都值得考虑这一工具。

技术栈不应成为创新的约束,而应是实现目标的工具。 Veaury正是这一理念的实践,让开发者能够专注于创造价值,而不是被框架之争所困扰。

Guigu 甑选平台第一篇:项目初始化与配置

第一章:项目创建 - 使用Create Vue的理由和步骤

步骤1:使用官方脚手架创建项目

使用npm create vue@latest是因为这是Vue团队官方维护的脚手架工具,能够确保项目结构与最新Vue特性完全兼容。它集成了Vue社区的最佳实践和推荐配置,减少了手动配置可能出现的错误。交互式命令行让开发者能够按需选择功能模块。

bash

复制下载

# 执行创建命令
npm create vue@latest

# 交互式配置
 Project name: ... guigu-zhenxuan-platform
 Add TypeScript? ... Yes  # 选择TypeScript是为了提供类型安全,减少运行时错误
 Add JSX Support? ... No  # 不使用JSX是因为Vue推荐使用模板语法,保持项目语法一致性
 Add Vue Router for Single Page Application development? ... Yes  # 添加Vue Router是因为SPA应用必须的路由管理
 Add Pinia for state management? ... Yes  # 选择Pinia是因为它是Vue官方推荐的状态管理库
 Add Vitest for Unit Testing? ... No  # 不先添加单元测试是为了先搭建项目基础架构,测试可以后期添加
 Add an End-to-End Testing Solution? ... No  # 不添加E2E测试是因为初期项目重点在功能开发
 Add ESLint for code quality? ... Yes  # 添加ESLint是为了统一代码风格,提高代码质量
 Add Prettier for code formatting? ... Yes  # 添加Prettier是为了自动格式化代码,避免团队成员间的格式争议

# 进入项目并安装基础依赖
cd guigu-zhenxuan-platform
npm install

初始化后的项目结构说明

text

复制下载

guigu-zhenxuan-platform/
├── src/
│   ├── components/    # components目录用于存放可复用的UI组件
│   ├── views/         # views目录用于存放页面级组件,这是Vue Router的惯例命名
│   ├── router/        # router目录用于集中管理路由配置
│   ├── stores/        # stores目录用于存放Pinia状态管理文件
│   └── main.ts        # main.ts是Vue应用的入口文件
├── public/            # public目录用于存放不需要构建处理的静态资源
├── .eslintrc.cjs      # ESLint配置文件,使用.cjs扩展名是因为需要CommonJS格式
├── .prettierrc        # Prettier代码格式化配置文件
├── index.html         # HTML入口文件,浏览器通过这个文件加载应用
├── package.json       # 项目配置文件,管理依赖和脚本
├── tsconfig.json      # TypeScript编译配置文件
└── vite.config.ts     # Vite构建工具配置文件

第二章:修改Package.json - 详细配置解析

步骤1:更新scripts配置

scripts配置决定了项目的开发工作流,合理的配置能提高开发效率。

打开package.json,修改scripts部分:

json

复制下载

"scripts": {
  "dev": "vite --open",
  // 配置vite --open是为了启动开发服务器后自动打开浏览器,提升开发体验
  
  "build": "run-p type-check "build-only {@}" --",
  // 这样配置build命令是为了并行执行类型检查和构建过程,提高构建速度
  
  "preview": "vite preview",
  // preview命令用于预览生产环境构建结果,验证构建效果是否符合预期
  
  "build-only": "vite build",
  // 单独的build-only命令用于纯构建操作,方便在组合命令中调用
  
  "type-check": "vue-tsc --build",
  // 使用vue-tsc是因为它专门针对Vue单文件组件进行TypeScript类型检查
  
  "lint": "run-s lint:*",
  // 使用run-s是为了顺序执行所有lint相关任务,确保代码检查的完整性
  
  "lint:oxlint": "oxlint . --fix",
  // 配置oxlint是因为它相比ESLint有更好的性能表现,检查速度更快
  
  "lint:eslint": "eslint . --fix --cache",
  // 保留ESLint是因为它有成熟的生态系统和丰富的插件支持
  
  "format": "prettier --write --experimental-cli src/",
  // 使用--experimental-cli参数是为了启用Prettier新版本的命令行特性
  
  "preinstall": "node ./scripts/preinstall.js"
  // preinstall脚本用于在安装依赖前检查开发环境是否符合要求
}

步骤2:添加生产依赖

生产依赖是项目运行时必须的包,每个依赖都有特定的业务用途。

执行以下安装命令:

bash

复制下载

# 安装Element Plus UI组件库
npm install element-plus
# 安装Element Plus是因为它提供了丰富的企业级UI组件,能显著加快开发速度

# 安装Element Plus图标库
npm install @element-plus/icons-vue
# 安装图标库是为了提供丰富的图标资源,提升用户界面视觉效果

# 安装Axios HTTP客户端
npm install axios
# 安装Axios是因为它是一个功能强大的HTTP客户端,支持请求拦截、响应拦截等高级特性

# 安装Mock.js数据模拟库
npm install mockjs
# 安装Mock.js是为了在开发阶段模拟后端API数据,实现前后端并行开发

package.json中的dependencies部分配置如下:

json

复制下载

"dependencies": {
  "@element-plus/icons-vue": "^2.3.2",  // Element Plus图标组件
  "axios": "^1.13.4",                    // HTTP请求库,用于API调用
  "element-plus": "^2.13.1",             // UI组件库,提供基础界面组件
  "mockjs": "^1.1.0",                    // 模拟数据生成器
  "pinia": "^2.1.7",                     // 状态管理库,已由create-vue安装
  "vue": "^3.4.21",                      // Vue核心框架,已由create-vue安装
  "vue-router": "^4.3.0"                 // 路由管理库,已由create-vue安装
}

步骤3:添加开发依赖

开发依赖只在开发阶段使用,用于提升开发体验和保证代码质量。

执行以下安装命令:

bash

复制下载

# 安装TypeScript相关配置
npm install --save-dev @tsconfig/node24
# 安装@tsconfig/node24是为了使用Node.js 24的TypeScript配置预设

npm install --save-dev @vue/tsconfig
# 安装@vue/tsconfig是为了使用Vue官方推荐的TypeScript配置预设

npm install --save-dev @types/node
# 安装@types/node是为了获取Node.js API的类型定义

# 安装Vite插件
npm install --save-dev vite-plugin-mock
# 安装vite-plugin-mock是为了将Mock数据集成到Vite开发服务器中

npm install --save-dev vite-plugin-svg-icons
# 安装vite-plugin-svg-icons是为了优化SVG图标的使用体验

npm install --save-dev vite-plugin-vue-devtools
# 安装vite-plugin-vue-devtools是为了增强Vue开发工具的功能

# 安装代码质量工具
npm install --save-dev eslint-config-prettier
# 安装eslint-config-prettier是为了集成Prettier和ESLint,避免规则冲突

npm install --save-dev eslint-plugin-oxlint
# 安装eslint-plugin-oxlint是为了在ESLint中使用oxlint规则

npm install --save-dev oxlint
# 安装oxlint是因为它提供了比ESLint更快的JavaScript代码检查

# 安装工具库
npm install --save-dev npm-run-all2
# 安装npm-run-all2是为了并行或顺序运行多个npm脚本

npm install --save-dev jiti
# 安装jiti是为了提供TypeScript文件的即时编译能力

完整的devDependencies配置如下:

json

复制下载

"devDependencies": {
  "@tsconfig/node24": "^24.0.4",           // Node.js 24的TypeScript配置预设
  "@types/node": "^20.12.7",              // Node.js API类型定义
  "@vitejs/plugin-vue": "^5.0.4",         // Vite的Vue单文件组件插件
  "@vue/eslint-config-typescript": "^13.0.0", // Vue项目的TypeScript ESLint配置
  "@vue/tsconfig": "^0.5.0",              // Vue项目的TypeScript配置
  "eslint": "^9.0.0",                     // JavaScript代码检查工具
  "eslint-config-prettier": "^9.1.0",     // 关闭与Prettier冲突的ESLint规则
  "eslint-plugin-oxlint": "~1.42.0",      // oxlint的ESLint插件
  "eslint-plugin-vue": "^9.23.0",         // Vue.js的ESLint插件
  "jiti": "^1.21.0",                      // TypeScript即时编译工具
  "npm-run-all2": "^8.0.4",               // 并行运行npm脚本的工具
  "oxlint": "~1.42.0",                    // 高性能JavaScript linter
  "prettier": "3.2.5",                    // 代码格式化工具
  "typescript": "~5.3.3",                 // TypeScript编译器
  "vite": "^5.2.0",                       // 前端构建工具
  "vite-plugin-mock": "^3.0.2",           // Vite的Mock数据插件
  "vite-plugin-svg-icons": "^2.0.1",      // Vite的SVG图标插件
  "vite-plugin-vue-devtools": "^7.3.0",   // Vite的Vue开发工具插件
  "vue-tsc": "^1.8.27"                    // Vue单文件组件的TypeScript检查器
}

步骤4:配置引擎要求和Prettier

在package.json末尾添加以下配置:

json

复制下载

"engines": {
  "node": "^20.19.0 || >=22.12.0"
},
// 配置engines是为了明确项目所需的Node.js版本范围,确保开发环境一致性

"prettier": {
  "ignorePath": ".prettierignore"
}
// 配置prettier是为了指定忽略文件配置,避免对特定文件进行格式化

第三章:创建环境检查脚本

步骤1:创建预安装脚本

预安装脚本在npm install之前执行,用于检查开发环境是否符合要求。

bash

复制下载

# 创建scripts目录
mkdir scripts

# 创建preinstall.js文件
touch scripts/preinstall.js

编辑scripts/preinstall.js文件:

javascript

复制下载

// 检查Node.js版本是否符合项目要求
const currentNodeVersion = process.versions.node;
const semver = currentNodeVersion.split('.');
const major = parseInt(semver[0], 10);

// 项目要求Node.js 20.19.0或更高版本
if (major < 20) {
  console.error(
    '你正在使用 Node.js ' +
      currentNodeVersion +
      '。\n' +
      '本项目需要 Node.js 20.19.0 或更高版本。\n' +
      '请升级你的 Node.js 版本。'
  );
  process.exit(1);  // 退出进程,阻止继续安装
}

console.log('✅ Node.js 版本检查通过');
// 版本检查通过后,npm install会继续执行

这个脚本的作用:确保所有开发者在一致的Node.js环境下工作,避免因版本差异导致的兼容性问题。

第四章:配置HTML入口文件

步骤1:修改index.html

index.html是Web应用的入口文件,浏览器通过加载这个文件启动整个应用。

编辑index.html文件:

html

复制下载运行

<!DOCTYPE html>
<html lang="zh-CN">
  <!-- 指定中文语言是为了更好的无障碍支持和SEO优化 -->
  
  <head>
    <meta charset="UTF-8">
    <!-- 设置UTF-8编码是为了支持中文等多语言字符 -->
    
    <link rel="icon" href="/favicon.ico">
    <!-- 设置网站图标,提升品牌识别度 -->
    
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- 配置viewport是为了实现响应式设计,适配移动设备 -->
    
    <title>硅谷甑选平台</title>
    <!-- 设置页面标题,显示在浏览器标签页上 -->
  </head>
  
  <body>
    <div id="app"></div>
    <!-- Vue应用挂载点,所有Vue组件将在这个div内渲染 -->
    
    <script type="module" src="/src/main.ts"></script>
    <!-- 使用type="module"启用ES模块支持,加载应用入口文件 -->
  </body>
</html>

第五章:配置TypeScript和Vite

步骤1:修改tsconfig.json

TypeScript配置文件决定了TypeScript编译器如何工作。

打开tsconfig.json,确保配置正确:

json

复制下载

{
  "extends": "@vue/tsconfig/tsconfig.dom.json",
  // 继承Vue官方的TypeScript配置,减少手动配置工作量
  
  "compilerOptions": {
    "target": "ES2020",
    // 设置编译目标为ES2020,使用较新的JavaScript特性
    
    "useDefineForClassFields": true,
    // 使用ES2022的类字段定义方式
    
    "module": "ESNext",
    // 使用ES模块系统,支持tree-shaking
    
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    // 包含的库文件,提供类型提示
    
    "skipLibCheck": true,
    // 跳过库文件的类型检查,加快编译速度
    
    "moduleResolution": "bundler",
    // 使用bundler的模块解析策略,与Vite保持一致
    
    "allowImportingTsExtensions": true,
    // 允许导入TypeScript扩展名的文件
    
    "resolveJsonModule": true,
    // 允许导入JSON文件作为模块
    
    "isolatedModules": true,
    // 确保每个文件都能单独编译
    
    "noEmit": true,
    // 不输出编译文件,由Vite处理构建
    
    "jsx": "preserve",
    // 保留JSX语法,由其他工具处理
    
    "strict": true,
    // 启用所有严格类型检查
    
    "noUnusedLocals": true,
    // 检查未使用的局部变量
    
    "noUnusedParameters": true,
    // 检查未使用的函数参数
    
    "noFallthroughCasesInSwitch": true,
    // 检查switch语句的fallthrough情况
    
    "baseUrl": ".",
    // 设置基础路径为当前目录
    
    "paths": {
      "@/*": ["./src/*"]
    }
    // 配置路径别名,@表示src目录,简化导入路径
  },
  
  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
  // 包含需要编译的文件类型
  
  "references": [
    {
      "path": "./tsconfig.node.json"
    }
    // 引用Node环境的TypeScript配置
  ]
}

步骤2:修改tsconfig.node.json

这个文件用于配置Node.js环境的TypeScript编译。

json

复制下载

{
  "extends": "@tsconfig/node24/tsconfig.json",
  // 继承Node.js 24的TypeScript配置预设
  
  "include": [
    "vite.config.ts",
    "scripts/**/*",
    "mock/**/*"
  ],
  // 包含Node环境下的TypeScript文件
  
  "compilerOptions": {
    "composite": true,
    // 启用复合编译,支持项目引用
    
    "noEmit": true,
    // 不输出编译文件
    
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo"
    // TypeScript构建信息文件位置
  }
}

步骤3:配置Vite构建工具

Vite配置文件决定了项目的构建行为和开发服务器配置。

打开vite.config.ts,修改为:

typescript

复制下载

import { fileURLToPath, URL } from 'node:url'
// 导入URL处理工具,用于处理文件路径
import { defineConfig } from 'vite'
// 导入Vite配置函数
import vue from '@vitejs/plugin-vue'
// 导入Vite的Vue插件,用于处理.vue文件
import { viteMockServe } from 'vite-plugin-mock'
// 导入Mock插件,用于开发阶段的数据模拟
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
// 导入SVG图标插件,优化图标使用
import VueDevTools from 'vite-plugin-vue-devtools'
// 导入Vue开发工具插件,增强调试能力
import path from 'path'
// 导入路径处理工具

export default defineConfig(({ command }) => ({
  // 根据命令模式(serve/build)返回不同配置
  
  plugins: [
    vue(),
    // Vue单文件组件插件,必须放在第一个
    
    viteMockServe({
      mockPath: 'mock',
      // Mock数据文件存放目录
      enable: command === 'serve',
      // 只在开发服务器启用Mock
    }),
    
    createSvgIconsPlugin({
      iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
      // SVG图标文件目录
      symbolId: 'icon-[dir]-[name]',
      // 图标ID生成规则
    }),
    
    VueDevTools(),
    // Vue开发工具插件
  ],
  
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
      // 配置路径别名,@指向src目录
    }
  },
  
  server: {
    port: 3000,
    // 开发服务器端口号
    open: true,
    // 启动后自动打开浏览器
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        // 代理目标地址
        changeOrigin: true,
        // 修改请求头中的Origin字段
        rewrite: (path) => path.replace(/^/api/, '')
        // 重写请求路径,移除/api前缀
      }
    }
  }
}))

第六章:配置代码质量和样式

步骤1:创建样式重置文件

样式重置文件用于统一不同浏览器的默认样式,提供一致的基准样式。

bash

复制下载

# 创建styles目录
mkdir src/styles

# 创建reset.css文件
touch src/styles/reset.css

编辑src/styles/reset.css文件:

css

复制下载

/* 重置所有元素的默认边距和内边距 */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  /* 使用border-box盒模型,更符合开发直觉 */
}

/* 设置根元素和body的高度 */
html, body {
  height: 100%;
  /* 确保页面能占满整个视口高度 */
  
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  /* 设置字体栈,优先使用系统字体 */
}

/* 设置Vue应用容器的样式 */
#app {
  height: 100%;
  /* 应用容器占满整个父元素高度 */
}

步骤2:修改ESLint配置

ESLint配置文件定义了代码检查规则,确保代码质量一致性。

打开.eslintrc.cjs,修改为:

javascript

复制下载

/* eslint-env node */
// 声明当前文件运行在Node.js环境中

require('@rushstack/eslint-patch/modern-module-resolution')
// 使用ESLint补丁,解决模块解析问题

module.exports = {
  root: true,
  // 指定为根配置文件,ESLint不会向上查找其他配置
  
  extends: [
    'plugin:vue/vue3-essential',
    // Vue 3基础规则集
    'eslint:recommended',
    // ESLint推荐规则
    '@vue/eslint-config-typescript',
    // Vue的TypeScript配置
    '@vue/eslint-config-prettier/skip-formatting'
    // 跳过Prettier的格式化规则
  ],
  
  parserOptions: {
    ecmaVersion: 'latest'
    // 使用最新的ECMAScript版本
  },
  
  rules: {
    'vue/multi-word-component-names': 'off'
    // 关闭Vue组件必须多单词命名的规则
    // 因为有些基础组件如Login、Home使用单单词更合适
  }
}

步骤3:创建.prettierignore文件

Prettier忽略文件指定了哪些文件不需要进行代码格式化。

bash

复制下载

# 创建Prettier忽略文件
touch .prettierignore

编辑.prettierignore文件:

plaintext

复制下载

node_modules
# 忽略node_modules目录,因为这是第三方依赖

dist
# 忽略构建输出目录

*.min.js
# 忽略压缩的JavaScript文件

*.min.css
# 忽略压缩的CSS文件

第七章:配置项目核心文件

步骤1:修改main.ts文件

main.ts是Vue应用的入口文件,负责初始化Vue应用并注册各种插件。

打开src/main.ts,修改为:

typescript

复制下载

import { createApp } from 'vue'
// 导入Vue的createApp函数,用于创建Vue应用实例

import './styles/reset.css'
// 导入重置样式,确保样式一致性

import App from './App.vue'
// 导入根组件

import router from './router'
// 导入路由配置

import { createPinia } from 'pinia'
// 导入Pinia的createPinia函数,用于创建状态存储

// 导入Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
// 导入Element Plus及其样式

import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 导入Element Plus的所有图标组件

// 创建Vue应用实例
const app = createApp(App)

// 创建Pinia状态存储实例
const pinia = createPinia()

// 注册Element Plus插件
app.use(ElementPlus)
// 注册路由
app.use(router)
// 注册Pinia状态管理
app.use(pinia)

// 注册所有Element Plus图标组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
  // 将每个图标注册为全局组件
}

// 将Vue应用挂载到HTML中的#app元素
app.mount('#app')

步骤2:修改App.vue文件

App.vue是应用的根组件,所有其他组件都在这个组件内渲染。

打开src/App.vue,修改为:

vue

复制下载

<script setup lang="ts">
// 使用<script setup>语法糖,简化组合式API的使用
// lang="ts"指定使用TypeScript

import { RouterView } from 'vue-router'
// 导入RouterView组件,用于渲染当前路由对应的组件
</script>

<template>
  <!-- 路由视图容器,根据当前路由显示不同的页面 -->
  <RouterView />
</template>

<style scoped>
/* scoped样式,只作用于当前组件 */
/* 可以在这里添加全局的样式规则 */
</style>

步骤3:配置路由

路由配置文件定义了应用的路由结构和页面导航逻辑。

打开src/router/index.ts,确保基本配置:

typescript

复制下载

import { createRouter, createWebHistory } from 'vue-router'
// 导入Vue Router的创建函数
// createWebHistory使用HTML5 History API,URL更美观

import type { RouteRecordRaw } from 'vue-router'
// 导入路由记录类型定义

// 定义路由数组,每个路由对应一个页面
const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    // 根路径
    redirect: '/login'
    // 重定向到登录页面,作为默认首页
  },
  {
    path: '/login',
    // 登录页面路径
    name: 'Login',
    // 路由名称,用于编程式导航
    component: () => import('@/views/LoginView.vue')
    // 使用动态导入实现路由懒加载,提高首屏加载速度
  }
  // 可以在这里添加更多路由配置
]

// 创建路由实例
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  // 使用history模式,需要服务器配置支持
  // import.meta.env.BASE_URL获取基础URL
  
  routes
  // 传入路由配置
})

// 导出路由实例,供main.ts使用
export default router

步骤4:配置Pinia Store

Pinia配置文件定义了应用的状态管理结构。

打开src/stores/index.ts,修改为:

typescript

复制下载

import { createPinia } from 'pinia'
// 导入createPinia函数,用于创建Pinia实例

// 创建Pinia实例
const pinia = createPinia()

// 导出Pinia实例,供main.ts使用
export default pinia

// 在这里可以导出具体的store模块
// 例如:export { useUserStore } from './user'
// 这样可以集中管理所有store的导出

第八章:创建Mock数据

步骤1:创建Mock目录和文件

Mock数据用于在开发阶段模拟后端API响应,实现前后端并行开发。

bash

复制下载

# 创建mock目录
mkdir mock

# 创建user mock文件
touch mock/user.ts

步骤2:配置Mock数据

编辑mock/user.ts文件:

typescript

复制下载

/*
 * @Description: Stay hungry,Stay foolish
 * @Author: Huccct
 * @Date: 2024-03-21
 */

// 模拟用户列表数据
const userList = [
  {
    id: 1,
    username: 'admin',
    password: '123456',
    name: '超级管理员',
    phone: '13800138000',
    roleName: '超级管理员',
    createTime: '2024-03-21',
    updateTime: '2024-03-21',
    status: 1,
  },
  {
    id: 2,
    username: 'test',
    password: '123456',
    name: '测试用户',
    phone: '13800138001',
    roleName: '普通管理员',
    createTime: '2024-03-21',
    updateTime: '2024-03-21',
    status: 1,
  },
]

export default [
  // 用户登录接口
  {
    url: '/api/user/login',
    method: 'post',
    response: ({ body }) => {
      const { username, password } = body
      const checkUser = userList.find(
        (item) => item.username === username && item.password === password,
      )
      if (!checkUser) {
        return { code: 201, data: { message: '账号或者密码不正确' } }
      }
      return { code: 200, data: {token:'Admin Token' }}
    },
  },
  // 获取用户信息
  {
    url: '/api/user/info',
    method: 'get',
    response: (request) => {
      const token = request.headers.token
      if (token === 'Admin Token') {
        return {
          code: 200,
          data: {
            name: 'admin',
            avatar:
              'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            roles: ['admin'],
            buttons: ['cuser.detail'],
            routes: [
              'home',
              'Acl',
              'User',
              'Role',
              'Permission',
              'Product',
              'Trademark',
              'Attr',
              'Spu',
              'Sku',
            ],
          },
          message: '获取用户信息成功',
        }
      }
      return {
        code: 201,
        data: null,
        message: '获取用户信息失败',
      }
    },
  },
  // 获取用户列表
  {
    url: '/api/acl/user/:page/:limit',
    method: 'get',
    response: ({ query }) => {
      const { username } = query
      let filteredList = userList
      if (username) {
        filteredList = userList.filter((user) =>
          user.username.includes(username),
        )
      }
      return {
        code: 200,
        data: {
          records: filteredList,
          total: filteredList.length,
        },
      }
    },
  },
  // 添加/更新用户
  {
    url: '/api/acl/user/save',
    method: 'post',
    response: ({ body }) => {
      const newUser = {
        ...body,
        id: userList.length + 1,
        createTime: new Date().toISOString().split('T')[0],
        updateTime: new Date().toISOString().split('T')[0],
        status: 1,
      }
      userList.push(newUser)
      return { code: 200, data: null, message: '添加成功' }
    },
  },
  {
    url: '/api/acl/user/update',
    method: 'put',
    response: ({ body }) => {
      const index = userList.findIndex((item) => item.id === body.id)
      if (index !== -1) {
        userList[index] = {
          ...userList[index],
          ...body,
          updateTime: new Date().toISOString().split('T')[0],
        }
      }
      return { code: 200, data: null, message: '更新成功' }
    },
  },
  // 删除用户
  {
    url: '/api/acl/user/remove/:id',
    method: 'delete',
    response: (request) => {
      const id = request.query.id
      if (!id) {
        return { code: 201, data: null, message: '参数错误' }
      }
      const index = userList.findIndex((item) => item.id === Number(id))
      if (index !== -1) {
        userList.splice(index, 1)
        return { code: 200, data: null, message: '删除成功' }
      }
      return { code: 201, data: null, message: '用户不存在' }
    },
  },
  // 批量删除用户
  {
    url: '/api/acl/user/batchRemove',
    method: 'delete',
    response: ({ body }) => {
      const { idList } = body
      idList.forEach((id) => {
        const index = userList.findIndex((item) => item.id === id)
        if (index !== -1) {
          userList.splice(index, 1)
        }
      })
      return { code: 200, data: null, message: '批量删除成功' }
    },
  },
  // 获取用户角色
  {
    url: '/api/acl/user/toAssign/:userId',
    method: 'get',
    response: () => {
      return {
        code: 200,
        data: {
          assignRoles: [
            {
              id: 1,
              roleName: '超级管理员',
              createTime: '2024-03-21',
              updateTime: '2024-03-21',
            },
          ],
          allRolesList: [
            {
              id: 1,
              roleName: '超级管理员',
              createTime: '2024-03-21',
              updateTime: '2024-03-21',
            },
            {
              id: 2,
              roleName: '普通管理员',
              createTime: '2024-03-21',
              updateTime: '2024-03-21',
            },
          ],
        },
      }
    },
  },
  // 分配用户角色
  {
    url: '/api/acl/user/doAssignRole',
    method: 'post',
    response: () => {
      return { code: 200, data: null, message: '分配角色成功' }
    },
  },
  // 用户登出接口
  {
    url: '/api/user/logout',
    method: 'post',
    response: () => {
      return { code: 200, data: null, message: '退出成功' }
    },
  },
]

第九章:总结

至此,你已经完成了Guigu致选平台项目的初始化配置。通过这个一步一步的教程,你应该能够:

  1. ✅ 使用create-vue脚手架创建项目
  2. ✅ 按照项目文档配置所有依赖
  3. ✅ 设置TypeScript和Vite配置
  4. ✅ 配置Element Plus和图标
  5. ✅ 设置Mock数据服务
  6. ✅ 创建基础的项目结构
  7. ✅ 启动并验证项目运行
❌