1. 账号注册 #

小程序注册地址

开发 -> 开发管理 -> 开发设置,获取所需的 AppId、AppSecret

2. 开发工具 #

下载地址

稳定版本,常规安装,微信扫码登录

3. 编辑器设置 #

4. 项目配置文件 #

4.1 project.config.json #

{
  // 微信小程序的应用ID。你需要在微信开放平台注册小程序,然后获取该ID。
  "appid": "wx2ad70a9c3b41c9a5",
  // 编译类型,此处指定为“miniprogram”意味着这是一个微信小程序项目。
  "compileType": "miniprogram",
  // 使用的微信小程序库的版本。
  "libVersion": "3.1.2",
  "packOptions": {
    // 指定哪些文件或者文件夹应该被打包工具忽略。
    "ignore": [],
    // 指定哪些文件或者文件夹应该被打包工具包括,即使它们被忽略规则所排除。
    "include": []
  },
  "setting": {
    // 如果为true,则会支持cover-view相关的设置。cover-view是微信小程序中的一个组件,可以覆盖在其他组件之上。
    "coverView": true,
    // 如果为true,则启用ECMAScript 6语法支持。
    "es6": true,
    // 如果为true,则启用PostCSS,它是一个CSS处理工具。
    "postcss": true,
    // 如果为true,则会压缩JavaScript代码。
    "minified": true,
    // 如果为true,会增强某些功能或者对一些特性进行优化。
    "enhance": true,
    // 如果为true,在wxml面板中会显示ShadowRoot。这与Web组件的Shadow DOM有关。
    "showShadowRootInWxmlPanel": true,
    // 设置与NPM包关系的列表。
    "packNpmRelationList": [],
    "babelSetting": {
      // Babel是一个JavaScript编译器,这里可以指定哪些文件或者文件夹不应该被Babel处理。
      "ignore": [],
      // 可以禁用某些Babel插件。
      "disablePlugins": [],
      // Babel的输出路径。
      "outputPath": ""
    }
  },
  // 设置条件编译的相关参数。
  "condition": {},
  "editorSetting": {
    // 设置编辑器的缩进模式。此处为“insertSpaces”,表示使用空格进行缩进。
    "tabIndent": "insertSpaces",
    // 设置编辑器的缩进大小。此处为2,表示使用两个空格进行缩进。
    "tabSize": 2
  }
}

4.2 project.private.config.json #

project.private.config.json

{
  "description": "项目私有配置文件", // 说明:这是一个用于描述项目的私有配置文件。
  "projectname": "miniclient", // 项目名称:这里的项目名称被设定为"miniclient"。
  "setting": {
    "compileHotReLoad": true, // 编译热重载设置:当设置为true时,项目在编译时会自动加载最新的更改,而无需每次更改后都重新编译。
    "urlCheck": false // URL检查设置:当设置为false时,系统不会执行URL的合法性检查。
  }
}

5.创建页面 #

5.1 app.json #

{
  "pages": [
    "pages/index/index",
    "pages/interview/interview",
    "pages/lesson/lesson",
    "pages/profile/profile"
  ]
}

5.2 pages\index #

5.3 pages\interview #

5.4 pages\lesson #

5.5 pages\profile #

6.Tabbar 配置 #

6.1 app.json #

app.json

{
  "pages": [
    "pages/index/index", // 定义首页路径
    "pages/interview/interview", // 定义面试题页面的路径
    "pages/lesson/lesson", // 定义课程中心页面的路径
    "pages/profile/profile" // 定义个人信息或个人中心页面的路径
  ],
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/index/index", // 指定页面路径
        "text": "首页", // 页签的文本
        "iconPath": "images/home.png", // 页签的图标路径
        "selectedIconPath": "images/home-selected.png" // 选中时的页签图标路径
      },
      {
        "pagePath": "pages/interview/interview",
        "text": "面试题",
        "iconPath": "images/interview.png",
        "selectedIconPath": "images/interview-selected.png"
      },
      {
        "pagePath": "pages/lesson/lesson",
        "text": "课程中心",
        "iconPath": "images/lesson.png",
        "selectedIconPath": "images/lesson-selected.png"
      },
      {
        "pagePath": "pages/profile/profile",
        "text": "我",
        "iconPath": "images/profile.png",
        "selectedIconPath": "images/profile-selected.png"
      }
    ]
  }
}

这个配置文件主要描述了小程序的页面路径及其底部的页签(tabBar)配置。

pages字段列出了小程序中所有的页面路径,

tabBar字段则用于配置显示在小程序底部的页签,并为每个页签提供了正常与选中状态的图标。

7.编译模式 #

在微信小程序开发中,添加编译模式可以让你快速预览或切换到某个特定的页面或场景,避免了每次都需要从首页进入。这在开发过程中尤其有用,尤其是当你的小程序有很多页面或者需要模拟特定的场景时。

以下是如何在微信开发者工具中添加编译模式的步骤:

  1. 打开微信开发者工具:首先确保你已经安装了微信开发者工具,并打开你的项目。

  2. 进入编译模式配置

    • 在工具的顶部菜单中,点击“项目”。
    • 在下拉菜单中选择“项目设置”。
    • 在弹出的窗口中,左侧你会看到“编译模式”这个选项。
  3. 添加新的编译模式

    • 在编译模式设置中,你会看到一个“+”图标或“添加编译模式”的按钮。
    • 点击这个按钮,你可以为这个编译模式定义一个名称,选择一个页面,并设置该页面的初始参数(如果需要的话)。
  4. 保存并使用编译模式

    • 完成上述设置后,点击保存。
    • 在微信开发者工具的主界面,你现在应该可以在顶部看到一个下拉菜单,列出了所有你已经定义的编译模式。
    • 选择其中一个,然后点击编译,微信开发者工具就会直接编译并预览到你所选择的页面或场景。
  5. 管理编译模式

    • 如果你想编辑或删除已经添加的编译模式,只需返回到“编译模式”设置,在那里你可以看到所有的编译模式列表,并进行相应的操作。

这样,通过添加不同的编译模式,你可以更方便地在开发过程中预览或切换到不同的页面或场景,提高开发效率。

8. 个人中心 #

8.1 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
    <image class="profile-avatar" src="{{avatarUrl}}"></image>
    <view class="profile-info">
      <text class="login-prompt-text">未登录</text>
      <button class="login-status-btn" bind:tap="loginUser">
        点我快捷登录
      </button>
    </view>
  </view>
</view>

8.2 profile.js #

pages\profile\profile.js

// 定义一个默认的头像URL常量
const defaultAvatarUrl = "/images/avatar.png";
// 定义页面对象
Page({
  // 页面的初始数据
  data: {
    // 使用默认头像URL初始化avatarUrl
    avatarUrl: defaultAvatarUrl,
  },
});
  1. const defaultAvatarUrl = "/images/avatar.png";

    • 这里定义了一个常量defaultAvatarUrl,并赋予其一个字符串值"/images/avatar.png"。这个字符串是一个相对路径,指向小程序项目中的一个默认头像图片。
  2. Page({ ... });

    • Page是微信小程序的一个内置函数,用于定义一个页面。当你浏览到这个页面时,微信小程序会执行这个Page函数,从而创建并显示页面。
  3. data: { ... },

    • Page对象中,data对象是一个特殊的对象。它定义了页面的初始数据。
    • 在页面的 WXML 模板中,你可以直接使用data对象中的数据来渲染页面。例如,你可以在模板中使用{{avatarUrl}}来引用这个avatarUrl数据。
  4. avatarUrl: defaultAvatarUrl,

    • 这里在data对象中定义了一个数据属性avatarUrl,并将其初始化为defaultAvatarUrl的值,即"/images/avatar.png"
    • 这意味着在页面加载时,avatarUrl的默认值会是"/images/avatar.png"。如果你在 WXML 模板中有一个<image>标签,如<image src="{{avatarUrl}}" />,它会默认显示该默认头像。

总结:这段代码定义了一个微信小程序的页面,该页面有一个初始数据avatarUrl,其值默认为"/images/avatar.png"。这很有可能用于显示用户的头像,而当用户没有上传头像时,程序会使用这个默认头像。

8.3 profile.wxss #

pages\profile\profile.wxss

.profile-login {
  background: #1296db;
  padding: 50rpx;
}

.profile-login-container {
  height: 250rpx;
  color: #fff;
  display: flex;
  align-items: center;
}

.profile-avatar {
  width: 150rpx;
  height: 150rpx;
  border-radius: 50%;
}

.profile-info {
  margin-left: 25rpx;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.login-status-btn {
  border-radius: 10rpx;
  text-align: center;
  margin-top: 30rpx;
  line-height: 70rpx;
}

9. 事件绑定 #

9.1 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
    <image class="profile-avatar" src="{{avatarUrl}}"></image>
+   <view class="profile-info" bind:tap="tapView">
     <text class="login-prompt-text">未登录</text>
+    <button class="login-status-btn"  bind:tap="tapButton">点我快捷登录</button>
    </view>
  </view>
</view>

9.2 profile.js #

pages\profile\profile.js

const defaultAvatarUrl = "/images/avatar.png";
Page({
  data: {
    avatarUrl: defaultAvatarUrl,
  },
+ tapView() {
+   console.log("tapView");
+ },
+ tapButton() {
+   console.log("tapButton");
+ },
});

10. 用户登录 #

10.1 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
    <image class="profile-avatar" src="{{avatarUrl}}"></image>
    <view class="profile-info">
      <text class="login-prompt-text">点我快捷登录</text>
+     <button class="login-status-btn"  bind:tap="loginUser">未登录</button>
    </view>
  </view>
</view>

10.2 profile.js #

pages\profile\profile.js

import { storeToken } from "../../utils/token";
import { get } from "../../utils/request";
const appInstance = getApp();
Page({
  data: {
    avatarUrl: appInstance.globalData.defaultAvatarUrl,
  },
  async loginUser() {
    try {
      const { code } = await new Promise((resolve, reject) => {
        wx.login({
          success: resolve,
          fail: reject,
        });
      });
      if (code) {
        const {
          data: { token, nickname, avatar },
        } = await get("/login", {
          data: { code },
        });
        storeToken(token);
      }
    } catch (error) {
      wx.showToast({
        title: error.message || "Login failed.",
        icon: "error",
        duration: 2000,
      });
    }
  },
});

10.3 app.js #

app.js

App({
  globalData: {
    defaultAvatarUrl: "/images/avatar.png",
    baseURL: "http://localhost:3000",
  },
});

10.4 token.js #

utils\token.js

export function fetchStoredToken() {
  return wx.getStorageSync("authToken");
}
export function storeToken(token) {
  wx.setStorageSync("authToken", token);
}
export function removeStoredToken() {
  wx.removeStorageSync("authToken");
}

10.5 request.js #

utils\request.js

import { fetchStoredToken, removeStoredToken } from "./token";
const appInstance = getApp();
function httpRequest(options) {
  const token = fetchStoredToken();
  if (token) {
    options.header = {
      ...options.header,
      Authorization: `Bearer ${token}`,
    };
  }
  return new Promise((resolve, reject) => {
    wx.request({
      ...options,
      url: `${appInstance.globalData.baseURL}${options.url}`,
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data);
        } else if (res.statusCode === 401) {
          removeStoredToken();
          wx.showModal({
            title: "认证失败",
            content: "请先登录",
            showCancel: false,
            success() {
              wx.navigateTo({
                url: "/pages/profile/profile",
              });
            },
          });
        } else {
          reject(new Error(`Request failed with status ${res.statusCode}`));
        }
      },
      fail: (err) => {
        reject(err);
      },
    });
  });
}
export function get(url, options = {}) {
  return httpRequest({
    url,
    ...options,
  });
}
export function post(url, options = {}) {
  return httpRequest({
    url,
    method: "POST",
    ...options,
  });
}
export default httpRequest;

11. 填写用户信息 #

11.1 profile.js #

pages\profile\profile.js

+import {
+  storeToken,
+  fetchStoredToken,
+  removeStoredToken,
+} from "../../utils/token";
import { get } from "../../utils/request";
const appInstance = getApp();
Page({
  data: {
    avatarUrl: appInstance.globalData.defaultAvatarUrl,
+   nickname: "",
+   avatar: "",
+   isLogin: false,
  },
+ onLoad() {
+   if (fetchStoredToken()) {
+     const { nickname, avatar } = wx.getStorageSync("user") || {};
+     this.setData({
+       nickname,
+       avatar,
+       isLogin: true,
+     });
+   }
+ },
+ logout() {
+   removeStoredToken();
+   wx.reLaunch({
+     url: "/pages/profile/profile",
+   });
+ },
  async loginUser() {
    try {
      const { code } = await new Promise((resolve, reject) => {
        wx.login({
          success: resolve,
          fail: reject,
        });
      });
      if (code) {
        const {
          data: { token, nickname, avatar },
        } = await get("/login", {
          data: { code },
        });
        storeToken(token);
+       if (nickname) {
+         this.setData({
+           avatar,
+           nickname,
+           isLogin: true,
+         });
+         wx.setStorageSync("user", { nickname, avatar });
+         wx.showToast({ title: `登录成功`, duration: 1000 });
+       } else {
+         const confirm = await wx.showModal({
+           title: "登录成功,您还未完善信息",
+           content: "去完善信息吧",
+           confirmText: "去完善",
+         });
+         if (confirm) {
+           return wx.navigateTo({ url: "/pages/improve/improve" });
+         }
+       }
      }
    } catch (error) {
      wx.showToast({
        title: error.message || "Login failed.",
        icon: "error",
        duration: 2000,
      });
    }
  },
});

11.2 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
+   <image class="profile-avatar" src="{{isLogin? avatar:  avatarUrl}}"></image>
+   <view class="profile-info" wx:if="{{isLogin}}">
+     <text>{{nickname}}</text>
+   </view>
+   <view class="profile-info" wx:else>
      <text class="login-prompt-text">点我快捷登录</text>
      <button class="login-status-btn" bind:tap="loginUser">未登录</button>
    </view>
  </view>
</view>
+<button bindtap="logout">退出</button>

11.3 improve.wxml #

pages\improve\improve.wxml

<form bindsubmit="submitUserInfo">
  <button
    class="avatar-wrapper"
    open-type="chooseAvatar"
    bind:chooseavatar="onChooseAvatar"
  >
    <image class="avatar" src="{{avatarUrl}}"></image>
  </button>
  <input name="nickname" type="nickname" placeholder="请输入昵称" />
  <button form-type="submit">提交</button>
</form>

11.4 improve.wxss #

pages\improve\improve.wxss

.avatar-wrapper {
  margin: 50rpx auto;
  width: 200rpx;
  height: 200rpx;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
}
.avatar {
  width: 200rpx;
  height: 200rpx;
}

11.5 improve.js #

pages\improve\improve.js

import { fetchStoredToken } from "../../utils/token";
const app = getApp();
Page({
  data: {
    avatarUrl: app.globalData.defaultAvatarUrl,
  },
  onChooseAvatar(event) {
    const { avatarUrl } = event.detail;
    this.setData({
      avatarUrl,
    });
  },
  submitUserInfo(event) {
    const token = fetchStoredToken();
    const { baseURL } = app.globalData;
    if (!token) {
      wx.showToast({
        title: "未授权",
        icon: "error",
      });
      return;
    }
    wx.uploadFile({
      filePath: this.data.avatarUrl,
      header: {
        Authorization: `Bearer ${token}`,
      },
      name: "avatar",
      url: `${baseURL}/upload`,
      formData: {
        nickname: event.detail.value.nickname,
      },
      fail: (err) => {
        console.error("上传失败:", err);
        wx.showToast({
          title: "上传失败",
          icon: "error",
        });
      },
      success: (res) => {
        try {
          const { nickname, avatar } = JSON.parse(res.data);
          wx.setStorageSync("user", { nickname, avatar });
          wx.switchTab({
            url: "/pages/profile/profile",
            success() {
              const pages = getCurrentPages();
              const currentPage = pages[pages.length - 1];
              currentPage.onLoad();
            },
          });
        } catch (error) {
          console.error("解析响应失败:", error);
          wx.showToast({
            title: "解析响应失败",
            icon: "error",
          });
        }
      },
    });
  },
});

12.使用组件库 #

vant-weapp

工具 => 构建 npm

12.1 安装 #

npm i @vant/weapp -S --production

12.2 profile.json #

pages\profile\profile.json

{
  "usingComponents": {
    "van-cell": "@vant/weapp/cell/index",
    "van-cell-group": "@vant/weapp/cell-group/index"
  }
}

12.3 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
    <image class="profile-avatar" src="{{isLogin? avatar:  avatarUrl}}"></image>
    <view class="profile-info" wx:if="{{isLogin}}">
      <text>{{nickname}}</text>
    </view>
    <view class="profile-info" wx:else>
      <text class="login-prompt-text">未登录</text>
      <button class="login-status-btn" bind:tap="loginUser">点我快捷登录</button>
    </view>
  </view>
</view>
+<van-cell-group inset wx:if="{{isLogin}}" >
+  <van-cell title="退出"  bind:click="logout" is-link/>
+  <van-cell title="收藏" is-link />
+</van-cell-group>

13.分享朋友圈 #

13.1 app.json #

app.json

{
  "pages": [
    "pages/index/index",
    "pages/interview/interview",
    "pages/lesson/lesson",
    "pages/profile/profile",
    "pages/improve/improve"
  ],
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "images/home.png",
        "selectedIconPath": "images/home-selected.png"
      },
      {
        "pagePath": "pages/interview/interview",
        "text": "面试题",
        "iconPath": "images/interview.png",
        "selectedIconPath": "images/interview-selected.png"
      },
      {
        "pagePath": "pages/lesson/lesson",
        "text": "课程中心",
        "iconPath": "images/lesson.png",
        "selectedIconPath": "images/lesson-selected.png"
      },
      {
        "pagePath": "pages/profile/profile",
        "text": "我",
        "iconPath": "images/profile.png",
        "selectedIconPath": "images/profile-selected.png"
      }
    ]
  },
+ "usingComponents": {
+   "van-button": "@vant/weapp/button/index"
+ }
}

13.2 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
    <image class="profile-avatar" src="{{isLogin? avatar:  avatarUrl}}"></image>
    <view class="profile-info" wx:if="{{isLogin}}">
      <text>{{nickname}}</text>
    </view>
    <view class="profile-info" wx:else>
      <text class="login-prompt-text">未登录</text>
      <button class="login-status-btn" bind:tap="loginUser">点我快捷登录</button>
    </view>
  </view>
</view>
<van-cell-group inset wx:if="{{isLogin}}">
  <van-cell title="退出" bind:click="logout" is-link />
  <van-cell title="收藏" is-link />
</van-cell-group>
+<van-button type="info" block open-type="share">分享前端面试</van-button>

13.3 profile.js #

pages\profile\profile.js

import {
  storeToken,
  fetchStoredToken,
  removeStoredToken,
} from "../../utils/token";
import { get } from "../../utils/request";
const appInstance = getApp();
Page({
  data: {
    avatarUrl: appInstance.globalData.defaultAvatarUrl,
    nickname: "",
    avatar: "",
    isLogin: false,
  },
  onLoad() {
+   wx.showShareMenu({
+     withShareTicket: true,
+     menus: ["shareAppMessage", "shareTimeline"],
+   });
    if (fetchStoredToken()) {
      const { nickname, avatar } = wx.getStorageSync("user") || {};
      this.setData({
        nickname,
        avatar,
        isLogin: true,
      });
    }
  },
+ onShareAppMessage() {
+   return {
+     title: "分享小程序",
+     path: "/pages/index/index",
+     success: function (res) {
+       console.log("分享成功", res);
+     },
+     fail: function (res) {
+       console.log("分享失败", res);
+     },
+    };
+ },
+ onShareTimeline() {
+   return {
+     title: "分享朋友圈"
+   };
+ },
  logout() {
    removeStoredToken();
    wx.reLaunch({
      url: "/pages/profile/profile",
    });
  },
  async loginUser() {
    try {
      const { code } = await new Promise((resolve, reject) => {
        wx.login({
          success: resolve,
          fail: reject,
        });
      });
      if (code) {
        const {
          data: { token, nickname, avatar },
        } = await get("/login", {
          data: { code },
        });
        storeToken(token);
        if (nickname) {
          this.setData({
            avatar,
            nickname,
            isLogin: true,
          });
          wx.setStorageSync("user", { nickname, avatar });
          wx.showToast({ title: `登录成功`, duration: 1000 });
        } else {
          const confirm = await wx.showModal({
            title: "登录成功,您还未完善信息",
            content: "去完善信息吧",
            confirmText: "去完善",
          });
          if (confirm) {
            return wx.navigateTo({ url: "/pages/improve/improve" });
          }
        }
      }
    } catch (error) {
      wx.showToast({
        title: error.message || "Login failed.",
        icon: "error",
        duration: 2000,
      });
    }
  },
});

14.页面配置 #

14.1 app.json #

app.json

{
  "pages": [
    "pages/index/index",
    "pages/interview/interview",
    "pages/lesson/lesson",
    "pages/profile/profile",
    "pages/improve/improve"
  ],
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "images/home.png",
        "selectedIconPath": "images/home-selected.png"
      },
      {
        "pagePath": "pages/interview/interview",
        "text": "面试题",
        "iconPath": "images/interview.png",
        "selectedIconPath": "images/interview-selected.png"
      },
      {
        "pagePath": "pages/lesson/lesson",
        "text": "课程中心",
        "iconPath": "images/lesson.png",
        "selectedIconPath": "images/lesson-selected.png"
      },
      {
        "pagePath": "pages/profile/profile",
        "text": "我",
        "iconPath": "images/profile.png",
        "selectedIconPath": "images/profile-selected.png"
      }
    ]
  },
  "usingComponents": {
    "van-button": "@vant/weapp/button/index"
  },
+  "window": {
+    "navigationBarBackgroundColor": "#000000",
+    "navigationBarTextStyle":"white",
+    "navigationBarTitleText": "前端面试",
+    "backgroundColor": "#EEEEEE",
+    "backgroundTextStyle":"dark",
+    "enablePullDownRefresh": false
+  }
}

14.2 index.json #

pages\index\index.json

{
  "usingComponents": {},
+ "navigationBarTitleText": "首页",
+ "enablePullDownRefresh": true
}

14.3 interview.json #

pages\interview\interview.json

{
  "usingComponents": {},
+ "navigationBarTitleText": "面试题"
}

14.4 lesson.json #

pages\lesson\lesson.json

{
  "usingComponents": {},
+ "navigationBarTitleText": "课程中心"
}

14.5 profile.json #

pages\profile\profile.json

{
  "usingComponents": {},
+ "navigationBarTitleText": "个人中心"
}

15.首页轮播图 #

15.1 index\index.js #

pages\index\index.js

+import { get } from "../../utils/request";
Page({
  data: {
+   bannerList: [],
  },
+ async getBannerList() {
+   const { bannerList } = await get("/bannerList");
+   this.setData({
+     bannerList,
+   });
+ },
+ async onLoad() {
+   this.getBannerList();
+ },
});

15.2 index.wxml #

pages\index\index.wxml

<swiper indicator-dots="{{true}}" autoplay circular interval="3000">
  <swiper-item
    wx:for="{{bannerList}}"
    wx:key="index"
    wx:for-item="item"
    wx:for-index="index"
  >
    <image src="{{item.url}}" mode="aspectFill" data-href="{{item.href}}" />
  </swiper-item>
</swiper>

15.3 index.wxss #

pages\index\index.wxss

swiper{
  width:100%;
  height:350rpx;
}
swiper image{
  width:100%;
  height:100%;
}

16.文章列表 #

16.1 app.wxss #

app.wxss

.container{
  width:95%;
  margin:0 auto;
}

16.2 index\index.js #

pages\index\index.js

import { get } from "../../utils/request";
Page({
  data: {
    bannerList: [],
+   articles: [],
+   total: 0,
+   page: 1,
+   limit: 5,
  },
  async getBannerList() {
    const { bannerList } = await get("/bannerList");
    this.setData({
      bannerList,
    });
  },
+ async getArticleList() {
+   const { articles, total } = await get("/articleList", {
+     data: {
+       page: this.data.page,
+       limit: this.data.limit,
+     },
+   });
+   this.setData({
+     articles,
+     total,
+   });
+ },
  async onLoad() {
    this.getBannerList();
+   this.getArticleList();
  },
});

16.3 index.json #

pages\index\index.json

{
+ "usingComponents": {
+   "article": "/components/article/article"
+ },
  "navigationBarTitleText": "首页"
}

16.4 index.wxml #

pages\index\index.wxml

<swiper indicator-dots="{{true}}" autoplay circular interval="3000">
  <swiper-item wx:for="{{bannerList}}" wx:key="index" wx:for-item="item" wx:for-index="index">
    <image src="{{item.url}}" mode="aspectFill" data-href="{{item.href}}" />
  </swiper-item>
</swiper>
+<view class="container">
+  <view class="title">经典文章</view>
+  <article space="space" wx:for="{{articles}}" wx:key="index" wx:for-item="article" article="{{article}}"></article>
+</view>

16.5 index.wxss #

pages\index\index.wxss

swiper{
  width:100%;
  height:350rpx;
}
swiper image{
  width:100%;
  height:100%;
}
+.title{
+  width:100%;
+  height:60rpx;
+  line-height: 60rpx;
+  text-align: center;
+}
+.space{
+  margin-bottom: 15rpx;
+}

16.6 article.wxml #

components\article\article.wxml

<view class="article space">
  <text class="title">{{article.title}}</text>
  <view class="author">
    <text>{{article.author}} | {{article.createTime}}</text>
  </view>
  <image class="poster" src="{{article.poster}}" mode="aspectFill" />
  <text class="content">{{article.content}}</text>
  <view class="views">
    <text>点赞:{{article.support}} | 观看:{{article.look}}</text>
  </view>
</view>

16.7 article.json #

components\article\article.json

{
  "component": true,
  "usingComponents": {}
}

16.8 article.js #

components\article\article.js

Component({
  externalClasses: ["space"],
  properties: {
    article: {
      type: Object,
      value: {},
    },
  },
  data: {},
  methods: {},
});

16.9 article.wxss #

components\article\article.wxss

.article {
  border: 1px solid #ccc;
  border-radius: 15rpx;
  padding: 25rpx;
}

.title {
  padding-top: 10px;
  font-size: 35rpx;
  font-weight: bold;
  line-height: 50rpx;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  display: inline-block;
}

.author {
  font-size: 25rpx;
  line-height: 45rpx;
}

.content {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  margin: 15rpx 0;
  font-size: 30rpx;
}

.poster {
  width: 100%;
  height: 300rpx;
  border-radius: 20rpx;
}

.views {
  color: #777;
  font-size: 25rpx;
}

17.上拉加载和下拉刷新 #

17.1 index\index.js #

pages\index\index.js

import { get } from "../../utils/request";
Page({
  data: {
    bannerList: [],
    articles: [],
    total: 0,
    page: 1,
    limit: 5,
  },
  async getBannerList() {
    const { bannerList } = await get("/bannerList");
    this.setData({
      bannerList,
    });
  },
  async getArticleList() {
    const { articles, total } = await get("/articleList", {
      data: {
        page: this.data.page,
        limit: this.data.limit,
      },
    });
    this.setData({
+     articles: [...this.data.articles, ...articles],
      total,
+     page: this.data.page + 1,
    });
  },
  async onLoad() {
    this.getBannerList();
    this.getArticleList();
  },
+ async onReachBottom() {
+   if (this.data.total === this.data.articles.length) return;
+   wx.showNavigationBarLoading();
+   await this.getArticleList();
+   wx.hideNavigationBarLoading();
+ },
+ async onPullDownRefresh() {
+   this.data.articles = [];
+   this.data.page = 1;
+   await this.getArticleList();
+   wx.stopPullDownRefresh();
+ },
});

18.文章详情 #

18.1 article.wxml #

components\article\article.wxml

+<view class="article space" bindtap="toDetail">
  <text class="title">{{article.title}}</text>
  <view class="author">
    <text>{{article.author}} | {{article.createTime}}</text>
  </view>
  <image class="poster" src="{{article.poster}}" mode="aspectFill" />
  <text class="content">{{article.content}}</text>
  <view class="views">
    <text>点赞:{{article.support}} | 观看:{{article.look}}</text>
  </view>
</view>

18.2 article.js #

components\article\article.js

Component({
  externalClasses: ["space"],
  properties: {
    article: {
      type: Object,
      value: {},
    },
  },
  data: {},
  methods: {
+   toDetail() {
+     this.triggerEvent("todetail", this.data.article);
+   },
  },
});

18.3 index.wxml #

pages\index\index.wxml

<swiper indicator-dots="{{true}}" autoplay circular interval="3000">
  <swiper-item wx:for="{{bannerList}}" wx:key="index" wx:for-item="item" wx:for-index="index">
    <image src="{{item.url}}" mode="aspectFill" data-href="{{item.href}}" />
  </swiper-item>
</swiper>
<view class="container">
  <view class="title">经典文章</view>
+ <article space="space" wx:for="{{articles}}" wx:key="index" wx:for-item="article" article="{{article}}" bind:todetail="toArticleDetail"></article>
</view>

18.4 index.js #

pages\index\index.js

import { get } from "../../utils/request";
Page({
  data: {
    bannerList: [],
    articles: [],
    total: 0,
    page: 1,
    limit: 5,
  },
  async getBannerList() {
    const { bannerList } = await get("/bannerList");
    this.setData({
      bannerList,
    });
  },
  async getArticleList() {
    const { articles, total } = await get("/articleList", {
      data: {
        page: this.data.page,
        limit: this.data.limit,
      },
    });
    this.setData({
      articles: [...this.data.articles, ...articles],
      total,
      page: this.data.page + 1,
    });
  },
  async onLoad() {
    this.getBannerList();
    this.getArticleList();
  },
  async onReachBottom() {
    if (this.data.total === this.data.articles.length) return;
    wx.showNavigationBarLoading();
    await this.getArticleList();
    wx.hideNavigationBarLoading();
  },
  async onPullDownRefresh() {
    this.data.articles = [];
    this.data.page = 1;
    await this.getArticleList();
    wx.stopPullDownRefresh();
  },
+ toArticleDetail(event) {
+   wx.navigateTo({
+     url: "/pages/article-detail/article-detail?id=" + event.detail._id,
+   });
+ }
});

18.5 article-detail.wxml #

pages\article-detail\article-detail.wxml

<view class="container">
    <text class="title">{{article.title}}</text>
    <view>
        <text>{{article.author}}</text>
        |
        <text>{{article.createTime}}</text>
    </view>
    <image class="poster" src="{{article.poster}}" mode="aspectFill" bind:tap="preview" />
    <text class="detail-content" user-select>{{article.detail}}</text>
</view>

18.6 article-detail.wxss #

pages\article-detail\article-detail.wxss

.title {
  font-size: 45rpx;
  padding: 20rpx 0;
}
.poster {
  width: 100%;
  height: 350rpx;
  margin: 20rpx 0;
}
.detail-content {
  font-size: 25rpx;
}

18.7 article-detail.json #

pages\article-detail\article-detail.json

{
  "usingComponents": {},
  "navigationBarTitleText": "文章详情"
}

18.8 article-detail.js #

pages\article-detail\article-detail.js

import { get } from "../../utils/request";
Page({
  data: {
    article: {},
  },
  async onLoad(options) {
    let id = options.id;
    let { article } = await get("/article", {
      data: {
        id,
      },
    });
    this.setData({
      article,
    });
  },
  preview() {
    wx.previewImage({
      urls: [this.data.article.poster],
    });
  },
});

19.标签选择 #

19.1 interview.wxml #

pages\interview\interview.wxml

<scroll-view scroll-x style="white-space: nowrap;">
  <view
    wx:for="{{tabs}}"
    wx:for-index="index"
    wx:key="index"
    data-index="{{index}}"
    bind:tap="tabChange"
    class="tab-item {{currentIndex === index ? 'active':''}}"
  >
    {{ item }}
  </view>
</scroll-view>

19.2 interview.wxss #

pages\interview\interview.wxss

.tab-item {
  display: inline-block;
  padding: 30rpx;
  border: 1px solid #ccc;
}
.active {
  background: #0099ff;
  color: #fff;
}

19.3 interview.js #

pages\interview\interview.js

Page({
  data: {
    tabs: ["css", "html", "js", "react", "vue", "typescript", "node"],
    currentIndex: 0,
  },
  changeCurrentIndex(currentIndex) {
    this.setData({
      currentIndex,
    });
  },
  tabChange(event) {
    this.changeCurrentIndex(event.currentTarget.dataset["index"]);
  },
});

20.轮播图选择 #

20.1 interview.wxml #

pages\interview\interview.wxml

<scroll-view scroll-x style="white-space: nowrap;">
  <view
    wx:for="{{tabs}}"
    wx:for-index="index"
    wx:key="index"
    data-index="{{index}}"
    bind:tap="tabChange"
    class="tab-item {{currentIndex === index ? 'active':''}}"
    >
    {{item}}
  </view>
</scroll-view>
+<swiper current="{{currentIndex}}" bind:change="swiperChange">
+  <swiper-item wx:for="{{tabs}}" wx:key="i" >
+    <view>{{item}}</view>
+  </swiper-item>
+</swiper>

20.2 interview.js #

pages\interview\interview.js

Page({
  data: {
    tabs: ["vue", "react", "typescript", "html", "css", "node"],
    currentIndex: 0,
  },
  changeCurrentIndex(currentIndex) {
    this.setData({
      currentIndex,
    });
  },
  tabChange(event) {
    this.changeCurrentIndex(event.currentTarget.dataset["index"]);
  },
+ swiperChange(event) {
+   this.changeCurrentIndex(event.detail.current);
+ },
});

21.获取面试题 #

21.1 interview.wxml #

pages\interview\interview.wxml

<scroll-view scroll-x style="white-space: nowrap;">
  <view wx:for="{{tabs}}" wx:for-index="index" wx:key="index" data-index="{{index}}" bind:tap="tabChange" class="tab-item {{currentIndex === index ? 'active':''}}">
    {{item}}
  </view>
</scroll-view>
<swiper current="{{currentIndex}}" bind:change="swiperChange">
  <swiper-item wx:for="{{tabs}}" wx:key="i">
+   <view wx:for="{{interviews}}" wx:key="i">
+     {{item.title}}
+   </view>
  </swiper-item>
</swiper>

21.2 interview.js #

pages\interview\interview.js

import { get } from "../../utils/request";
Page({
  data: {
    tabs: ["vue", "react", "typescript", "html", "css", "node"],
    currentIndex: 0,
+   interviews: [],
  },
  changeCurrentIndex(currentIndex) {
+   this.setData({ currentIndex }, this.getInterviews);
  },
  tabChange(event) {
    this.changeCurrentIndex(event.currentTarget.dataset["index"]);
  },
  swiperChange(event) {
    this.changeCurrentIndex(event.detail.current);
  },
+ async getInterviews() {
+   let type = this.data.tabs[this.data.currentIndex];
+   const { interview: interviews } = await get("/interview", {
+     data: { type },
+   });
+   this.setData({
+     interviews,
+   });
+ },
+ async onLoad() {
+   await this.getInterviews();
+ },
});

22.容器高度 #

22.1 interview.wxml #

pages\interview\interview.wxml

<scroll-view scroll-x style="white-space: nowrap;">
  <view wx:for="{{tabs}}" wx:for-index="index" wx:key="index" data-index="{{index}}" bind:tap="tabChange" class="tab-item {{currentIndex === index ? 'active':''}}">
    {{item}}
  </view>
</scroll-view>
+<swiper current="{{currentIndex}}" bind:change="swiperChange" style="height:{{swiperHeight}}px">
  <swiper-item wx:for="{{tabs}}" wx:key="i">
+   <view wx:for-index="index" class="swiper-item-{{index}}">
+     <view wx:for="{{interviews}}" wx:key="i" class="interview">{{item.title}}</view>
+   </view>
  </swiper-item>
</swiper>

22.2 interview.wxss #

pages\interview\interview.wxss

.tab-item {
  display: inline-block;
  padding: 30rpx;
  border: 1px solid #ccc;
}
.active {
  background: #0099ff;
  color: #fff;
}
+.interview {
+  height: 100rpx;
+}

22.3 interview.js #

pages\interview\interview.js

import { get } from "../../utils/request";
Page({
  data: {
    tabs: ["vue", "react", "typescript", "html", "css", "node"],
    currentIndex: 0,
    interviews: [],
+   swiperHeight: 150,
  },
  changeCurrentIndex(currentIndex) {
    this.setData({ currentIndex }, this.getInterviews);
  },
  tabChange(event) {
    this.changeCurrentIndex(event.currentTarget.dataset["index"]);
  },
  swiperChange(event) {
    this.changeCurrentIndex(event.detail.current);
  },
  async getInterviews() {
    let type = this.data.tabs[this.data.currentIndex];
    const { interview: interviews } = await get("/interview", {
      data: { type },
    });
+   this.setData(
+     {
+       interviews,
+     },
+     () => {
+       const query = wx.createSelectorQuery();
+       query
+         .select(`.swiper-item-${this.data.currentIndex}`)
+         .boundingClientRect();
+       query.exec((result) => {
+         const { height } = result[0];
+         this.setData({
+           swiperHeight: height,
+         });
+       });
+     }
+   );
+ },
  async onLoad() {
    await this.getInterviews();
  },
});

23.播放音频 #

23.1 app.json #

app.json

{
  "pages": [
    "pages/index/index",
    "pages/interview/interview",
    "pages/lesson/lesson",
    "pages/profile/profile",
    "pages/improve/improve",
    "pages/article-detail/article-detail"
  ],
  "tabBar": {
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "images/home.png",
        "selectedIconPath": "images/home-selected.png"
      },
      {
        "pagePath": "pages/interview/interview",
        "text": "面试题",
        "iconPath": "images/interview.png",
        "selectedIconPath": "images/interview-selected.png"
      },
      {
        "pagePath": "pages/lesson/lesson",
        "text": "课程中心",
        "iconPath": "images/lesson.png",
        "selectedIconPath": "images/lesson-selected.png"
      },
      {
        "pagePath": "pages/profile/profile",
        "text": "我",
        "iconPath": "images/profile.png",
        "selectedIconPath": "images/profile-selected.png"
      }
    ]
  },
  "usingComponents": {
    "van-button": "@vant/weapp/button/index"
  },
  "window": {
    "navigationBarBackgroundColor": "#000000",
    "navigationBarTextStyle": "white",
    "navigationBarTitleText": "前端面试",
    "backgroundColor": "#EEEEEE",
    "backgroundTextStyle": "dark",
    "enablePullDownRefresh": false
  },
+ "requiredBackgroundModes": ["audio"]
}

23.2 interview.wxml #

pages\interview\interview.wxml

<scroll-view scroll-x style="white-space: nowrap;">
  <view wx:for="{{tabs}}" wx:for-index="index" wx:key="index" data-index="{{index}}" bind:tap="tabChange" class="tab-item {{currentIndex === index ? 'active':''}}">
    {{item}}
  </view>
</scroll-view>
<swiper current="{{currentIndex}}" bind:change="swiperChange" style="height:{{swiperHeight}}px">
  <swiper-item wx:for="{{tabs}}" wx:key="i">
    <view wx:for-index="index" class="swiper-item-{{index}}">
+      <view wx:for="{{interviews}}" wx:key="i" class="interview">
+        <text>{{item.title}}</text>
+        <text>{{item.desc}}</text>
+        <view class="interview-operator">
+          <van-rate custom-class="rate" readonly value="{{item.hard}}" color="#ffd21e" void-icon="star" void-color="#eee" />
+          <image class="music" data-interview="{{item}}" src="{{playingId ==  item._id ? '/images/pause.png' : '/images/play.png'}}" bind:tap="togglePlay" />
+       </view>
+     </view>
    </view>
  </swiper-item>
</swiper>

23.3 interview.json #

pages\interview\interview.json

{
  "usingComponents": {
+    "van-rate": "@vant/weapp/rate/index"
  },
  "navigationBarTitleText": "面试题"
}

23.4 interview.wxss #

pages\interview\interview.wxss

.tab-item {
  display: inline-block;
  padding: 30rpx;
  border: 1px solid #ccc;
}
.active {
  background: #0099ff;
  color: #fff;
}
+.interview {
+  padding: 10rpx;
+}
+.interview-operator {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  height: 80rpx;
+  align-items: center;
+  border-bottom: 1rpx solid #ccc;
+  margin-bottom: 5rpx;
+}
+.rate {
+  display: flex;
+}
+.music {
+  width: 60rpx;
+  height: 60rpx;
+}

23.5 interview.js #

pages\interview\interview.js

import { get } from "../../utils/request";
Page({
  data: {
    tabs: ["vue", "react", "typescript", "html", "css", "node"],
    currentIndex: 0,
    interviews: [],
    swiperHeight: 150,
+   playingId: '',
+   audioManager: null,
  },
  changeCurrentIndex(currentIndex) {
    this.setData({ currentIndex }, this.getInterviews);
  },
  tabChange(event) {
    this.changeCurrentIndex(event.currentTarget.dataset["index"]);
  },
  swiperChange(event) {
    this.changeCurrentIndex(event.detail.current);
  },
  async getInterviews() {
    let type = this.data.tabs[this.data.currentIndex];
    const { interview: interviews } = await get("/interview", {
      data: { type },
    });
    this.setData(
      {
        interviews,
      },
      () => {
        const query = wx.createSelectorQuery();
        query
          .select(`.swiper-item-${this.data.currentIndex}`)
          .boundingClientRect();
        query.exec((result) => {
          const { height } = result[0];
          this.setData({
            swiperHeight: height,
          });
        });
      }
    );
  },
  async onLoad() {
+   this.getInterviews();
+   this.initAudioManager();
  },
+ initAudioManager() {
+   const audioManager = wx.getBackgroundAudioManager();
+   this.setData({ audioManager });
+   audioManager.onPause(() => {
+     this.setData({ playingId: -1 });
+   });
+ },
+ togglePlay(event) {
+   const { url, title, _id } = event.currentTarget.dataset.interview;
+   if(!url){
+     return wx.showToast({
+       title: '音频文件不存在',
+     })
+   }
+   const { audioManager, playingId } = this.data;
+   if (playingId === _id) {
+     audioManager.pause();
+     this.setData({ playingId: -1 });
+   } else {
+     audioManager.title = title;
+     audioManager.src = url;
+     this.setData({ playingId: _id });
+   }
+ },
+ stopAudio() {
+   this.data.audioManager.stop();
+ },
+ onHide() {
+   this.stopAudio();
+ },
+ onUnload() {
+   this.stopAudio();
+ },
});

24.课程列表 #

24.1 lesson.wxml #

pages\lesson\lesson.wxml

<van-card
  wx:for="{{lessons}}"
  wx:key="i"
  desc="{{item.desc}}"
  title="{{item.title}}"
  thumb="{{ item.poster }}"
  thumb-mode="aspectFill"
>
  <view slot="footer">
    <van-button size="small" data-url="{{item.url}}">
      观看
    </van-button>
    <van-button size="small">收藏</van-button>
  </view>
</van-card>

24.2 lesson.json #

pages\lesson\lesson.json

{
  "usingComponents": {
    "van-card": "@vant/weapp/card/index",
    "van-button": "@vant/weapp/button/index"
  },
  "navigationBarTitleText": "课程中心"
}

24.3 lesson.js #

pages\lesson\lesson.js

import { get } from "../../utils/request";
Page({
  data: {
    lessons: [],
  },
  async onLoad() {
    let { lesson: lessons } = await get("/lesson");
    this.setData({
      lessons,
    });
  },
});

25.视频播放 #

25.1 lesson.wxml #

pages\lesson\lesson.wxml

+<video class="lessonVideo" src="{{url}}" autoplay></video>
<van-card wx:for="{{lessons}}" wx:key="i" desc="{{item.desc}}" title="{{item.title}}" thumb="{{ item.poster }}" thumb-mode="aspectFill">
    <view slot="footer">
+       <van-button size="small" data-url="{{item.url}}" bind:tap="watch">观看</van-button>
        <van-button size="small">收藏</van-button>
    </view>
</van-card>

25.2 lesson.wxss #

pages\lesson\lesson.wxss

+.lessonVideo {
+  width: 100%;
+}

25.3 lesson.js #

pages\lesson\lesson.js

import { get } from "../../utils/request";
Page({
  data: {
    lessons: [],
+   url: "",
  },
  async onLoad() {
    let { lesson: lessons } = await get("/lesson");
    this.setData({
      lessons,
    });
  },
+ watch(event) {
+   let { url } = event.currentTarget.dataset;
+   this.setData({
+     url,
+   });
+ },
});

26.搜索 #

26.1 lesson.wxml #

pages\lesson\lesson.wxml

+<van-search value="{{ value }}" placeholder="请输入搜索关键词" show-action bind:search="onSearch" />
<video class="lessonVideo" src="{{url}}" autoplay></video>
<van-card wx:for="{{filteredLessons}}" wx:key="i" desc="{{item.desc}}" title="{{item.title}}" thumb="{{ item.poster }}" thumb-mode="aspectFill">
    <view slot="footer">
        <van-button size="small" data-url="{{item.url}}" bind:tap="watch">观看</van-button>
        <van-button size="small">收藏</van-button>
    </view>
</van-card>

26.2 lesson.json #

pages\lesson\lesson.json

{
  "usingComponents": {
    "van-card": "@vant/weapp/card/index",
    "van-button": "@vant/weapp/button/index",
+   "van-search": "@vant/weapp/search/index"
  },
  "navigationBarTitleText": "课程中心"
}

26.3 lesson.js #

pages\lesson\lesson.js

import { get } from "../../utils/request";
Page({
  data: {
    lessons: [],
+   filteredLessons: [],
    url: "",
+   value: "",
  },
  async onLoad() {
    let { lesson: lessons } = await get("/lesson");
    this.setData({
      lessons,
+     filteredLessons: lessons,
    });
  },
  watch(event) {
    let { url } = event.currentTarget.dataset;
    this.setData({
      url,
    });
  },
+  onSearch(event) {
+    let { lessons } = this.data;
+    if (event.detail == "") return this.setData({ filteredLessons: lessons });
+    this.setData({
+      filteredLessons: lessons.filter((item) =>
+        item.title.includes(event.detail)
+      ),
+    });
+  },
});

27.收藏 #

27.1 lesson.wxml #

pages\lesson\lesson.wxml

<van-search value="{{ value }}" placeholder="请输入搜索关键词" show-action bind:search="onSearch" />
<video class="lessonVideo" src="{{url}}" autoplay></video>
<van-card wx:for="{{filteredLessons}}" wx:key="i" desc="{{item.desc}}" title="{{item.title}}" thumb="{{ item.poster }}" thumb-mode="aspectFill">
+   <view slot="footer" mark:lesson="{{item}}">
        <van-button size="small" data-url="{{item.url}}" bind:tap="watch">观看</van-button>
+       <van-button size="small" bind:tap="addFavorite">收藏</van-button>
    </view>
</van-card>

27.2 lesson.js #

pages\lesson\lesson.js

import { get } from "../../utils/request";
+const app = getApp();
Page({
  data: {
    lessons: [],
    filteredLessons: [],
    url: "",
    value: "",
  },
  async onLoad() {
    let { lesson: lessons } = await get("/lesson");
    this.setData(
      {
        lessons,
        filteredLessons: lessons,
      },
+    () => this.playLesson()
    );
  },
+ playLesson() {
+   let { lessonId } = app.globalData;
+   if (lessonId) {
+     const lesson = this.data.lessons.find((item) => item._id === lessonId);
+     if (lesson) {
+       this.setData({ url: lesson.url });
+     }
+   }
+ },
+ onShow() {
+   this.playLesson();
+ },
  watch(event) {
    let { url } = event.currentTarget.dataset;
    this.setData({
      url,
    });
  },
  onSearch(event) {
    let { lessons } = this.data;
    if (event.detail == "") return this.setData({ filteredLessons: lessons });

    this.setData({
      filteredLessons: lessons.filter((item) =>
        item.title.includes(event.detail)
      ),
    });
  },
  addFavorite(event) {
    let { lesson } = event.mark;
    const favorites = wx.getStorageSync("favorites") || {};
    favorites[lesson._id] = lesson;
    wx.setStorageSync("favorites", favorites);
  },
});

27.3 profile.wxml #

pages\profile\profile.wxml

<view class="profile-login">
  <view class="profile-login-container">
    <image class="profile-avatar" src="{{isLogin? avatar:  avatarUrl}}"></image>
    <view class="profile-info" wx:if="{{isLogin}}">
      <text>{{nickname}}</text>
    </view>
    <view class="profile-info" wx:else>
      <text class="login-prompt-text">未登录</text>
      <button class="login-status-btn" bind:tap="loginUser">点我快捷登录</button>
    </view>
  </view>
</view>
<van-cell-group inset wx:if="{{isLogin}}">
  <van-cell title="退出" bind:click="logout" is-link />
+ <van-cell title="收藏" is-link url="/pages/favorites/favorites" />
</van-cell-group>
<van-button type="info" block open-type="share">分享前端面试</van-button>

27.4 favorites.wxml #

pages\favorites\favorites.wxml

<van-cell
  wx:for="{{lessons}}"
  wx:key="i"
  title="{{item.title}}"
  mark:lesson="{{item}}"
  bind:click="watchLesson"
  is-link
></van-cell>

27.5 favorites.json #

pages\favorites\favorites.json

{
  "usingComponents": {
    "van-cell": "@vant/weapp/cell/index",
    "van-cell-group": "@vant/weapp/cell-group/index"
  }
}

27.6 favorites.js #

pages\favorites\favorites.js

const app = getApp();
Page({
  data: {
    lessons: [],
  },
  onLoad() {
    let lessons = wx.getStorageSync("favorites");
    if (!lessons) return;
    this.setData({
      lessons: Object.values(lessons),
    });
  },
  watchLesson(event) {
    app.globalData.lessonId = event.mark.lesson._id;
    wx.switchTab({
      url: "/pages/lesson/lesson",
    });
  },
});

28.参考 #

1.常用组件 #

1.1. view: #

<view class="container">
  <text>Hello</text>
</view>

1.2. image: #

<image src="/path/to/image.jpg" mode="aspectFit"/>

其中,modeaspectFit表示按原图比例显示,图片可能会有留白。

1.3. text: #

<text> This is some text. </text>

2.rpx #

在微信小程序中,rpx(Responsive Pixel)是一个专门为屏幕适配设计的长度单位。由于移动设备有着各种各样的屏幕尺寸和分辨率,使用rpx单位可以更容易地实现多屏适配。

以下是一些关于rpx的关键点:

  1. 定义:

    • rpx意为“响应式像素”。
    • 设计目的是能够让开发者不用关心手机的屏幕大小,编写一次代码,UI 在所有屏幕上都能做到合适的适配。
  2. 基准:

    • 在宽度为 375 像素的设备上,1rpx等于1px(像素)。
    • 在微信小程序中,规定屏幕宽度分成 750 份,这就意味着在 iPhone6 上,屏幕宽度为 375px,所以 1rpx 等于 0.5px。
  3. 自适应:

    • 由于rpx是响应式的,它会根据实际设备的屏幕宽度进行自动缩放。
    • 例如,如果某设备的屏幕宽度是 750px,那么1rpx会等于1px;如果屏幕宽度是 500px,那么1rpx会等于500/750 px
  4. 使用场景:

    • rpx尤其适用于跨多种手机型号、尺寸和分辨率的 UI 布局。
    • 对于固定不变的元素,例如图标或特定的边界,仍然可以使用px作为单位。
  5. 转换:

    • 很多时候,设计师会提供基于某一特定屏幕尺寸的设计稿(如基于 iPhone6 的 375px 宽度)。开发者可以将设计稿上的 px 值转换为 rpx 值。例如,设计稿上一个元素宽度为 187.5px,那么在微信小程序中应该设置为 375rpx。

示例:

.container {
  width: 750rpx; /* 宽度填满屏幕 */
  height: 300rpx; /* 高度为屏幕宽度的2/5 */
}

总之,使用rpx单位是微信小程序为了简化移动端屏幕适配问题而引入的一个特色设计。在开发过程中合理使用它,可以有效地保证用户界面在不同设备上的一致性和良好表现。

3.tap #

在微信小程序中,事件处理是核心的交互手段。bind:tapcatch:tap都是用于处理“点击”事件的,但它们之间存在一些差异。下面是对这两个事件处理器的详细解释:

  1. bind:tap:

    • bind:tap是一个常用的事件处理器,用于监听元素的点击事件。
    • 当一个元素被点击时,与其关联的bind:tap事件处理函数会被调用。
    • 事件是冒泡的。这意味着,如果你在一个元素上点击,该事件不仅仅会触发这个元素上的bind:tap,还会触发其父级元素上的bind:tap,直到它到达页面的根元素或被一个catch:tap捕获。

    示例:

    <view bind:tap="handleTap">点击我</view>
    
  2. catch:tap:

    • catch:tap也是用于监听元素的点击事件的。
    • bind:tap的主要差异在于:catch:tap会“捕获”事件,阻止它进一步冒泡。
    • 这在某些情况下是非常有用的,例如,你可能有一个元素覆盖在另一个元素上,并且你只想处理上层元素的点击事件,而不想触发下层元素的点击事件。

    示例:

    <view bind:tap="parentTap">
      <view catch:tap="childTap">点击我</view>
    </view>
    

    在上述示例中,当你点击内部的view元素时,childTap函数会被调用,但由于catch:tap的存在,parentTap函数不会被触发。

总结

在开发过程中,应根据具体需求选择使用bind:tap还是catch:tap

4.wx.login #

wx.login 是微信小程序提供的一个核心 API,它允许开发者发起微信用户登录,获取用户登录态。这个登录态在小程序中是非常关键的,因为它通常是获取用户信息、调用微信支付等高级功能的前提条件。

以下是wx.login的详细解释:

主要功能:

  1. 获取 Code:

    • 当调用 wx.login 方法时,微信会返回一个临时的登录凭证 code
    • 这个 code 可以被传送到开发者的服务器,然后服务器可以与微信服务器交互,进一步获取用户的 openIdsessionKey 甚至 unionId(如果微信账户与其他应用已经关联)。
  2. 替代微信用户信息的显式授权:

    • 在以前,如果想获取用户的基本信息(如昵称、头像等),需要明确地让用户点击一个按钮进行授权。
    • 而通过 wx.login 和后续的服务器交互,开发者可以在用户不进行显式授权的情况下获取到其 openId

使用方法:

wx.login({
  success: (res) => {
    if (res.code) {
      // 可以将 res.code 发送到后台,通过code获取用户的 sessionKey 等相关信息
    } else {
      console.log("登录失败!" + res.errMsg);
    }
  },
  fail: (error) => {
    // 登录失败的处理
  },
});

注意事项:

  1. 临时性:
    • 通过 wx.login 获取的 code 是临时的,只能使用一次,所以每次需要与后台交互获取用户信息时,都要重新调用 wx.login
  2. 安全性:

    • 不应该直接在小程序前端使用 code 获取 openIdsessionKey,因为这涉及到 AppSecret,会存在泄露的风险。
    • 推荐的做法是:将 code 发送到开发者自己的后台,然后在后台与微信服务器进行交互。
  3. 与 wx.getUserInfo 的关系:

    • 通过 wx.login 只能获取到用户的 openIdsessionKey
    • 如果需要获取用户的其他信息(如昵称、头像等),则需要使用 wx.getUserInfo。但请注意,wx.getUserInfo 需要用户的明确授权。

总的来说,wx.login 是微信小程序中的核心登录功能,与后端的配合使用可以安全高效地获取到用户的登录态和基本信息。

5.wx.request #

wx.request 是微信小程序中提供的一个网络请求 API,允许开发者向服务器发送请求,获取或提交数据。该 API 是开发微信小程序时经常用到的,因为它是小程序与后台服务器通信的主要手段。

以下是wx.request的详细解释:

主要功能:

使用方法:

基本用法:

wx.request({
  url: "https://example.com/data", // 开发者服务器接口地址
  method: "GET", // 请求方法,默认为GET,有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
  data: { key: "value" }, // 请求的参数
  header: {
    "content-type": "application/json", // 默认值
  },
  success: (res) => {
    console.log(res.data); // 服务器返回的数据
  },
  fail: (error) => {
    // 请求失败的处理
  },
  complete: (res) => {
    // 请求完成后的回调函数,无论成功还是失败都会执行
  },
});

注意事项:

  1. 域名要求

    • 出于安全考虑,微信小程序要求开发者在微信开发者后台配置服务器域名,只有配置过的域名才能被小程序请求。
    • 此外,这些域名必须提供 HTTPS 服务。
  2. 数据格式

    • 默认的请求header'content-type': 'application/json',但根据需要也可以设置为其他值,如 'content-type': 'application/x-www-form-urlencoded'
  3. 并发限制

    • 微信小程序当前对于网络请求的并发限制是 10 个,超过这个数量的请求会被排队。
  4. 请求超时

    • wx.request 提供了 timeout 参数来设置请求的超时时间。
  5. 错误处理

    • 服务器返回的状态码不在 2xx 范围内都会判断为失败,会进入 fail 回调,例如 404、500 等。
  6. 跨域问题

    • 由于微信小程序的特殊性,它不受浏览器同源策略的限制,因此你可以请求任何已配置的域名,无需服务器设置 CORS。

示例:

获取服务器数据示例:

wx.request({
  url: "https://example.com/data",
  success: function (res) {
    console.log(res.data);
  },
});

提交表单数据示例:

wx.request({
  url: "https://example.com/submit",
  method: "POST",
  data: {
    username: "abc",
    password: "123",
  },
  header: {
    "content-type": "application/x-www-form-urlencoded",
  },
  success: function (res) {
    console.log(res.data);
  },
});

总的来说,wx.request 是微信小程序中非常重要的一个 API,它为小程序与后端服务的数据交互提供了基础能力。在开发过程中,正确和高效地使用它是关键。

6. wx.showToast #

wx.showToast 是微信小程序中的一个常用 API,用于显示一个短暂的、不会打断用户操作的消息提示框。这个 API 特别适用于给用户提供一些简单的反馈,比如“保存成功”、“操作失败”之类的提示。

以下是 wx.showToast 的详细讲解:

主要功能:

常用参数:

  1. title (必填):提示的内容,为字符串类型。
  2. icon (可选):图标,有效值有 "success", "loading", "none"。默认为 "success"。
  3. image (可选):自定义图标的本地路径,优先级高于 icon。
  4. duration (可选):提示的延迟时间,单位毫秒,默认为 1500ms。
  5. mask (可选):是否显示透明蒙层,防止触摸穿透,默认为 false。

示例:

  1. 显示一个成功提示:
wx.showToast({
  title: "保存成功",
  icon: "success",
  duration: 2000,
});
  1. 显示一个自定义图标的提示:
wx.showToast({
  title: "警告",
  image: "/path/to/your/icon.png", // 提供自定义图标的路径
  duration: 2000,
});
  1. 显示一个无图标的纯文本提示:
wx.showToast({
  title: "网络错误",
  icon: "none",
  duration: 2000,
});

注意事项:

总的来说,wx.showToast 是一个轻量级的、用于向用户提供反馈的提示框。在小程序开发中,它是一个非常实用和频繁使用的工具。

7. wx.showModal #

wx.showModal 是微信小程序中的一个 API,用于显示模态对话框。这个模态对话框通常用于向用户提供信息、警告或询问用户意见。它包含一个标题、内容区域,以及一个或两个按钮(通常为“取消”和“确定”)。

以下是关于 wx.showModal 的详细解释:

主要功能:

使用方法:

基本用法:

wx.showModal({
  title: "提示", // 模态对话框的标题
  content: "这是一个模态弹窗", // 模态对话框的内容
  showCancel: true, // 是否显示取消按钮,默认为true
  cancelText: "取消", // 取消按钮的文字,默认为"取消"
  cancelColor: "#000000", // 取消按钮的文字颜色
  confirmText: "确定", // 确定按钮的文字,默认为"确定"
  confirmColor: "#3CC51F", // 确定按钮的文字颜色
  success: (result) => {
    if (result.confirm) {
      console.log("用户点击确定");
    } else if (result.cancel) {
      console.log("用户点击取消");
    }
  },
  fail: () => {
    console.log("模态对话框显示失败");
  },
});

注意事项:

  1. 按钮的处理
    • wx.showModal 会返回用户的操作:confirmcancel。通过检查 result.confirm 的值,你可以知道用户是点击了“确定”按钮,还是“取消”按钮。
  2. 颜色规范

    • 设置按钮文字颜色时,需要使用合法的 CSS 颜色值,如 #FFFFFFred 等。
  3. 异步操作

    • wx.showModal 是一个异步操作,意味着它不会阻塞代码的执行。当用户做出选择后(点击“确定”或“取消”),success 回调函数会被触发。
  4. 无返回值
    • wx.showModal 不返回任何值,也就是说,你不能使用变量去接收它的返回结果。要处理用户的选择,必须使用 success 回调。

总结:wx.showModal 是微信小程序中的一个非常实用的 API,它提供了一种简单且直观的方式向用户显示信息或获取用户的确认。正确使用此 API 可以提高用户体验并使小程序交互更为流畅。

8. getApp #

getApp 是微信小程序的一个核心 API,它允许你获取全局的小程序实例。当你在微信小程序中调用 App 函数定义小程序行为时,你实际上是在创建一个小程序实例。而 getApp 则允许你在小程序的任何地方访问这个实例。

以下是关于 getApp 的详细解释:

主要功能:

使用方法:

基本用法:

const appInstance = getApp();

console.log(appInstance.globalData); // 输出在App对象中定义的globalData

注意事项:

  1. 生命周期

    • 通过 getApp 获取的小程序实例与 App 对象的生命周期相同。也就是说,它会在小程序启动时创建,并在小程序关闭时销毁。
  2. 全局数据共享

    • 使用 getApp 是访问全局数据和函数的主要方式。在 App 对象中定义的任何数据和函数都可以通过 getApp 在任何页面或组件中访问。
  3. 避免过度依赖

    • 虽然 getApp 提供了方便的全局数据访问方式,但应避免将大量数据存储在 App 对象中,因为这可能会导致数据管理变得复杂。适当地使用页面和组件的数据和状态更有利于维护和代码的可读性。
  4. 确保小程序实例存在

    • 在某些情况下,特别是当代码执行的时间点很早时(例如在某些模块的顶部),getApp 可能会返回 undefined。在这种情况下,你应确保在使用 getApp 返回的数据或函数之前,小程序实例已经被创建。

总结:getApp 是微信小程序中一个非常有用的 API,允许你轻松访问全局的小程序实例及其数据和函数。然而,正确地使用它并避免过度依赖全局数据是实现高效、可维护和易于理解的小程序的关键。

9.页面栈 #

微信小程序的页面栈是一个关键概念,它涉及到页面的导航、加载、返回和卸载。页面栈基于“栈”这一数据结构,用于管理小程序中的页面导航历史。

以下是关于微信小程序的页面栈的详细解释:

主要功能:

基本概念

  1. 页面入栈

    • 当打开一个新页面时(如使用 wx.navigateTo 方法),该页面会被压入页面栈的顶部。
  2. 页面出栈

    • 当关闭一个页面时(如使用 wx.navigateBack 方法或用户点击左上角的返回按钮),当前页面会从页面栈的顶部弹出,并被销毁。
  3. 页面重定向

    • 使用 wx.redirectTo 方法会关闭当前页面并跳转到目标页面,这意味着当前页面会从栈中移除,而目标页面会压入栈顶。
  4. 页面替换

    • 使用 wx.reLaunch 方法可以关闭所有页面并打开目标页面,这会清空整个页面栈并只留下目标页面。
  5. Tab 切换

    • 当使用 wx.switchTab 方法切换到一个 tabBar 页面时,这会清空页面栈并将目标 tabBar 页面设为栈底。

注意事项:

  1. 栈的大小限制

    • 微信小程序中,页面栈最多只能有 10 个页面。如果超过这个限制,需要采用其它方式进行页面导航,如使用 wx.redirectTowx.reLaunch
  2. 获取当前页面栈

    • 可以使用 getCurrentPages() 函数来获取当前的页面栈。这个函数会返回一个数组,其中第一个元素是栈底页面,最后一个元素是栈顶页面。
  3. 栈顶页面

    • 在任何时候,栈顶的页面都是用户当前看到的页面。
  4. 生命周期和页面栈

    • 当页面入栈时,页面会触发其 onLoadonShow 生命周期方法。
    • 当页面出栈时,页面会触发其 onUnload 生命周期方法。
    • 当页面由栈顶变为非栈顶状态(如有新的页面入栈覆盖当前页面)时,会触发 onHide 生命周期方法。

总结:微信小程序的页面栈是管理页面导航历史的核心机制。通过理解和合理利用页面栈,开发者可以实现复杂的页面导航逻辑并优化用户体验。

10. wx.navigateTo #

wx.navigateTo 是微信小程序中用于页面导航的核心 API 之一。它允许开发者从当前页面打开一个新页面。在小程序的页面栈中,这将会把新页面压入栈顶。

以下是关于 wx.navigateTo 的详细解释:

主要功能:

使用方法:

基本用法:

wx.navigateTo({
  url: "/pages/example/example", // 目标页面的路径。前面可以有'/'也可以没有
  success: (res) => {
    // 页面跳转成功后的回调
  },
  fail: (error) => {
    // 页面跳转失败的回调
    console.log(error);
  },
  complete: () => {
    // 页面跳转完成后的回调,无论成功还是失败都会执行
  },
});

注意事项:

  1. 页面栈的限制
    • 微信小程序中,页面栈最多只能有 10 个页面。当已经有 10 个页面时,使用 wx.navigateTo 将不会跳转,需要使用其它方式,如 wx.redirectTowx.reLaunch
  2. 页面路径

    • url 参数中提供的路径,可以是相对于当前页面的路径,也可以是相对于项目根目录的路径。但建议始终使用从项目根目录开始的绝对路径,以避免潜在的路径解析错误。
  3. 传递参数

    • 你可以在 url 中传递参数,例如:/pages/example/example?id=1&name=test。在被打开的页面中,你可以通过 onLoad 函数的参数来接收这些传递过来的参数。
  4. wx.redirectTo 的区别

    • 使用 wx.navigateTo 打开新页面时,当前页面会被保存,用户可以通过点击返回按钮或执行 wx.navigateBack 返回。
    • wx.redirectTo 则是关闭当前页面并跳转到新页面,使当前页面被卸载。
  5. 关闭页面

    • 使用 wx.navigateTo 打开的新页面可以通过 wx.navigateBack 或用户点击返回按钮来关闭并返回上一个页面。

总结:wx.navigateTo 是微信小程序中进行页面间跳转的基本方法。正确使用它可以确保良好的用户体验和页面间的顺畅导航。

11.switchTab #

wx.switchTab 是微信小程序中的一个 API,它专为切换 tab 页面而设计。在微信小程序中,tab 页面通常位于小程序的底部导航栏,并提供了快速切换到主要功能或页面的方式。

以下是关于 wx.switchTab 的详细解释:

主要功能:

使用方法:

基本用法:

wx.switchTab({
  url: "/pages/example/example", // 目标tabBar页面的路径
  success: () => {
    // 切换成功后的回调
  },
  fail: (error) => {
    // 切换失败的回调
    console.log(error);
  },
  complete: () => {
    // 切换完成后的回调,无论成功还是失败都会执行
  },
});

注意事项:

  1. URL 指定
    • wx.switchTab 中指定的 url 必须是定义在 app.json 中的 tabBar 页面之一。否则,这个方法调用会失败。
  2. 页面栈的影响

    • 当使用 wx.switchTab 切换到另一个 tabBar 页面时,当前的页面栈会被清空,并且目标 tabBar 页面会成为新的栈底页面。
  3. 不支持 URL 中传递参数

    • 由于 wx.switchTab 是切换到一个 tabBar 页面,它不支持在 url 中传递查询参数。如果需要在 tab 页面之间传递数据,你需要使用其他方法,如全局数据、本地存储等。
  4. 与其他导航方法的区别

    • wx.navigateTowx.redirectTo 不同,wx.switchTab 专门用于切换 tab 页面,而不是打开新页面或重定向到另一个非 tab 页面。
  5. 动画效果

    • 使用 wx.switchTab 切换 tab 页面时,微信小程序会自动提供一个平滑的切换动画。

总结:wx.switchTab 是微信小程序中切换 tab 页面的核心 API。通过使用它,开发者可以为用户提供一种快速、流畅的方式在主要功能或页面之间切换。正确理解和使用这个 API 是实现高质量、用户友好的小程序的关键。

12. wx.relaunch #

wx.reLaunch 是微信小程序中的一个核心 API,用于关闭所有页面,并打开至应用的某个页面。它在某些场景下非常有用,如当用户被登出或应用需要进行完全的重启时。

以下是关于 wx.reLaunch 的详细解释:

主要功能:

使用方法:

基本用法:

wx.reLaunch({
  url: "/pages/home/home", // 你想要重启至的页面路径
  success: () => {
    // 重启并切换成功后的回调
  },
  fail: (error) => {
    // 重启失败的回调
    console.log(error);
  },
  complete: () => {
    // 重启完成后的回调,无论成功还是失败都会执行
  },
});

注意事项:

  1. 页面路径
    • 你需要确保在 wx.reLaunch 中指定的 url 是有效的。无效的路径将导致重启失败。
  2. 页面栈清空

    • 调用 wx.reLaunch 后,当前的所有页面都会被关闭,新页面成为栈底页面。这意味着用户不能通过返回按钮回到先前的页面。
  3. 支持 URL 参数

    • wx.navigateTowx.redirectTo 一样,wx.reLaunch 支持在 url 中传递查询参数。这可以在需要重启应用并传递某些信息到新页面时使用。
  4. 与其他导航方法的区别

    • wx.reLaunch 和其他导航方法(如 wx.navigateTowx.redirectTowx.switchTab)的主要区别在于它会清空整个页面栈并启动新的页面作为栈底页面。
  5. 使用场景

    • 这个 API 在某些特定场景下特别有用,例如:
      • 当用户被登出后,需要重启至登录页面。
      • 当应用遇到无法恢复的错误时,需要重启至首页。
      • 在某些复杂的导航逻辑中,需要清空整个页面栈并导航到新的页面。

总结:wx.reLaunch 是微信小程序中用于实现完全重启的 API。它提供了一种快速、简洁的方式来关闭所有页面并导航到新的页面,从而实现整个应用的重启。正确理解和使用这个 API 可以帮助开发者在需要的场景下提供更好的用户体验。

13.setData #

在微信小程序中,datasetData 是与页面和组件状态管理紧密相关的核心概念。

1. data:

data 是页面或组件的一个对象,用于存储页面或组件的初始状态值。这些状态值与页面或组件的.wxml模板中的数据绑定相关联,当data中的数据发生变化时,视图会相应地更新。

例如,在页面或组件的 JS 文件中,你可能有如下定义:

Page({
  data: {
    message: "Hello World!",
    count: 0,
  },
  // ... 其他函数和生命周期方法
});

在上面的例子中,data 对象定义了两个属性:messagecount

2. setData:

setData 是页面或组件的一个核心方法,用于更改 data 中的数据,并告知框架需要更新视图。当你需要改变页面或组件的状态时,直接修改 data 对象是不够的,因为这样不会触发视图更新。你必须使用 setData 方法。

使用 setData 的例子:

Page({
  data: {
    message: "Hello World!",
    count: 0,
  },

  changeMessage: function () {
    this.setData({
      message: "New Message!",
    });
  },

  incrementCount: function () {
    this.setData({
      count: this.data.count + 1,
    });
  },

  // ... 其他函数和生命周期方法
});

在上述代码中,changeMessage 函数使用 setData 更改了 message 的值,而 incrementCount 函数使 count 值增加了 1。每次调用 setData,页面的视图都会相应地更新。

注意事项:

  1. 性能考虑

    • setData 是一个相对昂贵的操作,因为它可能会触发视图的重新渲染。因此,建议合并多个数据更改为单个 setData 调用,而不是连续使用多个 setData
  2. 异步操作

    • setData 是异步的。这意味着在调用 setData 之后,数据不会立即更新。如果你需要在数据更新后执行某些操作,可以使用 setData 的回调。
  3. 深度数据路径

    • 使用 setData 时,可以使用深度数据路径来更新嵌套对象中的属性。例如,this.setData({'obj.key': value})

总结:在微信小程序中,datasetData 是状态管理的基石。通过理解它们的工作原理和正确使用它们,开发者可以轻松管理页面和组件的状态,并确保视图与数据保持同步。

14.wx:elif&wx:else #

14.1 wx:elif #

wx:elif 是“else if”的简写形式,用于在wx:if的基础上提供一个额外的条件分支。如果wx:if的条件为假(false),wx:elif 提供了一个额外的条件检查并在满足该条件时渲染内容。

示例代码:

<view wx:if="{{score > 80}}">Excellent</view>
<view wx:elif="{{score > 60}}">Good</view>
<view wx:elif="{{score > 40}}">Pass</view>
<view wx:else>Fail</view>

在这个例子中:

wx:elif 可以有多个,每个 wx:elif 用一个新的条件表达式来决定是否渲染这个分支。

14.2 wx:else #

wx:else 用于在前面的 wx:ifwx:elif 条件都不满足的情况下渲染内容。它不需要自己的条件表达式,因为它在前面所有的条件都不满足的情况下被渲染。

续上面的例子,如果所有给定的条件都不满足(即 score <= 40),则会渲染 "Fail"。如果其中一个条件满足了,那么对应的内容块将被渲染,并且跳过剩下的所有wx:elifwx:else块。

注意

  1. 正确配对: wx:ifwx:elifwx:else 必须正确配对。一个 wx:else 必须和一个 wx:ifwx:elif 配对,而且它们必须是紧邻的,不能被其他的元素分隔开。
  2. 渲染性能: wx:if 是具有“惰性”的,如果初始条件为假,它内部的块级元素和组件不会被触发和渲染,直到条件为真。如果经常需要切换的情况下,建议使用 hidden 属性来控制显示/隐藏,因为使用 hidden 属性会保持组件状态,而非重新触发组件的生命周期。

使用 wx:if/wx:elif/wx:else 可以帮助你创建动态的、响应不同状态的用户界面,同时也需要确保你的代码保持整洁和可维护。

15.wx.uploadFile #

wx.uploadFile 是微信小程序中一个非常有用的 API,它允许开发者将本地的文件(通常是图片、音频、视频文件等)上传到开发者服务器。这个 API 非常适用于实现类似用户上传头像、分享图片等功能。

基础用法

wx.uploadFile 接收一个对象作为参数,该对象中包含一些选项来控制文件上传的行为。其中,最基础且必须的两个参数是 urlfilePath

示例代码

下面是一个基本的 wx.uploadFile 的示例代码:

wx.uploadFile({
  url: "https://example.com/upload", // 开发者服务器 URL
  filePath: "/path/to/file.jpg", // 本地文件路径
  name: "example", // 文件对应的 key
  formData: {
    // 额外的表单字段
    user: "test",
  },
  success(res) {
    const data = res.data; // 服务器返回的数据
    // ...
  },
  fail(err) {
    // 上传失败的处理
  },
  complete() {
    // 上传完成后(无论成功或失败)的回调
  },
});

参数详解

除了基础用法外,wx.uploadFile 还有一些可选参数可以进一步控制上传行为:

返回值及其方法

wx.uploadFile 返回一个 UploadTask 对象,这个对象表示一个上传任务,并提供了一些方法用来控制上传流程:

注意点

16.open-type #

在微信小程序中,open-type<button> 组件的一个属性,用于指定按钮的点击行为。open-type 提供了一系列的值,用于触发不同的微信功能。以下是一些常见的 open-type 取值及其描述:

常见的 open-type 取值

  1. contact:打开客服会话。如果希望用户在点击按钮后能够与客服进行交流,可以使用此值。

  2. share:触发用户转发。在按钮点击事件中可以获取到一个用于自定义转发内容的对象。

  3. getUserInfo:获取用户信息。点击按钮后,用户会收到一个获取头像昵称等信息的请求,同意后,开发者可以获得这些信息。

  4. getPhoneNumber:获取用户手机号。点击按钮后,用户会收到一个获取手机号的请求,同意后,开发者可以获得这些信息。

  5. launchApp:打开另一个小程序。需要在 app.jsonnavigateToMiniProgramAppIdList 字段内声明需要跳转的小程序列表。

  6. openSetting:打开小程序设置页面。用户可以在此页面中开启或关闭某些权限。

  7. feedback:打开“意见反馈”页面。

  8. navigate:进行页面的跳转。

使用示例

下面是一些使用 open-type 的基本示例:

使用 open-type 实现客服消息功能:

<button open-type="contact">联系客服</button>

使用 open-type 实现用户转发功能:

<button open-type="share">分享给朋友</button>

在 Page 对象的 onShareAppMessage 函数中你可以自定义转发内容。

使用 open-type 获取用户信息:

<button open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo">获取用户信息</button>

在处理函数 handleGetUserInfo 中你可以获取到用户的信息。

Page({
  // ...
  handleGetUserInfo(e) {
    console.log(e.detail.errMsg);
    console.log(e.detail.userInfo);
    console.log(e.detail.rawData);
  },
});

注意事项

17.chooseAvatar #

chooseAvatar

18.form #

在微信小程序中,form 组件是用于提交一些用户的输入信息至服务器的一个非常有用的组件。关于你提到的 bindsubmit="submitUserInfo",这里涉及到两个部分:form 组件的基本使用以及 bindsubmit 的具体用法。

1. form 组件

form 组件常常用来搭配各种输入组件如 input, checkbox, slider 等,用以提交表单数据。下面是一个基本的例子:

<form bindsubmit="submitUserInfo">
  <input name="username" type="text" placeholder="请输入用户名"/>
  <button type="submit">提交</button>
</form>

2. bindsubmit="submitUserInfo"

这里的 bindsubmit="submitUserInfo" 表示当用户触发表单提交事件(例如点击了类型为 submit 的按钮)的时候,会调用页面实例下名为 submitUserInfo 的方法。

在页面的 JS 代码中,你可以如下实现 submitUserInfo 方法:

Page({
  submitUserInfo: function (e) {
    console.log(e.detail.value); // 输出表单所有name值为key的对象
    var username = e.detail.value.username; // 获取输入框内容
    // 你可以在这里进行进一步的处理,例如验证输入内容的有效性,或者将数据提交到服务器等。
  },
});

注意事项

19.wx.showShareMenu #

wx.showShareMenu 是微信小程序中用于触发分享功能的 API。微信小程序是微信提供的一种轻应用形式,它允许开发者创建一些在微信内运行的小型应用。wx.showShareMenu 主要用于显示微信原生的分享按钮,并允许用户将小程序的内容分享给微信好友或者分享到朋友圈。

在微信小程序的开发中,调用分享功能并自定义分享内容通常是一个常见的需求。wx.showShareMenu 使得小程序能够轻松地集成分享功能。

以下是一个基本的 wx.showShareMenu 使用方法:

wx.showShareMenu({
  withShareTicket: true,
  menus: ["shareAppMessage", "shareTimeline"],
});

让我们深入了解这个方法的参数:

如果你希望自定义分享内容,可以在页面的脚本部分定义 onShareAppMessage 和/或 onShareTimeline 函数:

Page({
  onShareAppMessage: function (res) {
    return {
      title: "自定义分享标题",
      path: "/page/user?id=123",
    };
  },
  onShareTimeline: function () {
    return {
      title: "自定义分享朋友圈的标题",
      query: "foo=bar",
    };
  },
});

20.onShareAppMessage #

在微信小程序中,onShareAppMessage 是一个用来自定义用户点击分享按钮或使用右上角菜单“转发”时所展示的分享内容的函数。这个函数允许你自定义分享标题、分享图像、分享路径等内容。此方法需要返回一个对象,用于配置分享内容。

基础用法

以下是 onShareAppMessage 的一个基础使用实例:

Page({
  onShareAppMessage: function (res) {
    return {
      title: "自定义转发标题",
      path: "/page/user?id=123",
      imageUrl: "/path/to/image.jpg", // 图片路径
    };
  },
});

在这个实例中,当用户点击分享按钮时,他们会看到一个分享卡片,该卡片具有自定义的标题、路径和图像。

参数说明

onShareAppMessage 函数的参数 res 中,也包含了一些有用的信息,例如:

例子

下面的例子展示了一个更加具体的用法,当用户点击不同的按钮进行分享时,可以展示不同的分享内容:

Page({
  onShareAppMessage: function (res) {
    if (res.from === "button") {
      // 来自页面内转发按钮
      console.log(res.target);
    }
    return {
      title: "自定义转发标题",
      path: "/page/user?id=123",
    };
  },
});

通过检查 res.fromres.target 的值,你可以确定分享的来源,并做出相应的逻辑处理。希望这些信息能够帮助你更好地使用 onShareAppMessage 方法来自定义微信小程序的分享功能!

21.onShareTimeline #

onShareTimeline 是微信小程序的一个生命周期函数,用于处理用户通过“分享到朋友圈”分享内容时的事件。在微信小程序中,如果你希望自定义用户分享到朋友圈的内容(比如自定义标题、分享的页面路径或者自定义图片),你可以在页面脚本中使用 onShareTimeline 函数来返回一个自定义的对象。

基础用法

下面的代码展示了 onShareTimeline 的一个基础用法:

Page({
  onShareTimeline: function () {
    return {
      title: "自定义朋友圈分享标题",
      query: "key=value",
      imageUrl: "/path/to/image.jpg",
    };
  },
});

参数说明

onShareTimeline 中你可以返回一个对象,该对象可以包括以下字段:

示例

考虑以下使用示例,当用户选择在朋友圈分享内容时,它将展示自定义的标题、图片,并在用户点击分享内容进入小程序时携带自定义的参数:

Page({
  onShareTimeline: function () {
    return {
      title: "探索美食世界",
      query: "from=shareTimeline",
      imageUrl: "/images/share.jpg",
    };
  },
});

在这个例子中:

通过利用 onShareTimeline 函数,你可以在用户分享你的小程序内容到朋友圈时提供更加丰富和个性化的内容。希望这些信息能够帮助你更有效地使用这个功能!

22.生命周期 #

22.1 onLaunch #

onLaunch(options) 是微信小程序的全局生命周期钩子,它不是页面级别的生命周期,而是小程序整体的生命周期。这个生命周期钩子会在小程序启动时触发,通常是在用户点击小程序图标启动小程序时执行。下面是关于 onLaunch 的详细解释:

  1. onLaunch(options)
    • 当小程序启动时触发,这是整个小程序的第一个生命周期钩子。
    • options 参数包含了小程序启动时的参数信息,通常包括场景值、启动路径、分享信息等。
    • 通常用于进行一些全局的初始化操作,例如设置全局变量、配置全局数据、获取用户信息等。
    • 注意,onLaunch 是小程序全局的,只会在小程序启动时触发一次,不会随着页面切换而触发。

onLaunch 中,你可以执行一些全局设置和初始化工作,例如初始化全局数据、登录验证、获取用户信息等操作。这个生命周期钩子通常用于确保小程序在启动时的准备工作,以便后续页面可以顺利运行。例如:

App({
  onLaunch(options) {
    // 在小程序启动时执行的初始化操作
    console.log("小程序启动了");
    // 获取用户信息
    wx.getUserInfo({
      success: function (res) {
        console.log(res.userInfo);
      },
    });
  },
});

22.2 页面生命周期 #

微信小程序的页面生命周期钩子是一组函数,用于在不同阶段执行特定的操作。这些生命周期钩子提供了对小程序页面生命周期的控制,允许开发者在不同的阶段执行初始化、数据加载、页面展示等操作。下面是这些生命周期钩子的解释:

  1. onLoad(options)

    • 当页面被加载时触发。
    • options参数包含了页面跳转时的参数。
    • 通常用于页面的初始化工作,可以根据传递的参数做不同的初始化操作。
  2. onReady()

    • 当页面初次渲染完成时触发。
    • 可以在这个生命周期中执行一些与页面渲染相关的操作,如获取节点信息、动画等。
  3. onShow()

    • 当页面显示时触发。
    • 可以在这个生命周期中执行一些需要在页面显示时更新的操作,如数据刷新、监听事件等。
  4. onHide()

    • 当页面隐藏时触发,通常是因为用户跳转到了其他页面。
    • 可以在这个生命周期中执行一些清理工作或停止一些页面相关的操作,以节省资源。
  5. onUnload()

    • 当页面被卸载时触发,通常是因为用户返回了上一个页面或关闭了小程序。
    • 可以在这个生命周期中执行一些清理工作、停止定时器等资源释放操作。
  6. onPullDownRefresh()

    • 当用户下拉刷新页面时触发。
    • 可以在这个生命周期中执行数据刷新操作,通常会调用 wx.stopPullDownRefresh() 来停止下拉刷新动画。
  7. onReachBottom()

    • 当页面上拉触底时触发。
    • 通常用于实现分页加载更多数据的功能,可以在这个生命周期中加载下一页的数据。
  8. onShareAppMessage(options)

    • 当用户点击分享按钮或右上角分享菜单时触发。
    • 返回一个包含分享信息的对象,用于自定义分享内容和行为。
    • 通过这个生命周期钩子,你可以自定义分享的标题、图片、路径等信息,实现自定义的分享逻辑。

这些生命周期钩子允许你在不同的时机执行特定的代码,以掌握小程序页面的生命周期,实现更好的用户体验和功能交互。根据不同的需求,你可以在这些生命周期钩子中添加相应的代码来完成页面的初始化、数据加载、事件监听等操作。

23.wx:for #

wx:for 是微信小程序中的一个重要的指令,用于在 WXML 模板中进行列表渲染。它允许你遍历一个数组或对象,并将其中的每个元素渲染为对应的组件或内容。这个指令使得在小程序页面中动态展示数据变得非常方便。以下是关于 wx:for 的详细解释:

使用语法:

<view wx:for="{{array}}" wx:key="uniqueKey">
  <!-- 每次迭代的内容 -->
</view>

例如,假设你有一个包含学生姓名的数组 students,你可以使用 wx:for 来将每个学生的姓名渲染到页面上:

<view wx:for="{{students}}" wx:key="id">
  {{item.name}}
</view>

其中,students 是你的数据源,wx:key 指定了一个唯一标识符,这里使用了 id 字段。在每次迭代中,item 代表当前元素,你可以访问其属性,例如 item.name 来渲染学生的姓名。

需要注意的是,wx:for 是一种在小程序模板中进行循环渲染的强大工具,但要小心不要在大型列表中滥用,以免影响性能。此外,确保指定的 wx:key 是唯一的且稳定的,以避免渲染问题。

如果你需要在 wx:for 循环中执行更复杂的操作,你可以使用 wx:for-itemwx:for-index 来指定迭代项的别名和索引,以方便操作。例如:

<view wx:for="{{students}}" wx:key="id">
  <text>索引:{{index}}</text>
  <text>姓名:{{item.name}}</text>
</view>

在这个例子中,index 表示当前迭代的索引,item 表示当前迭代的元素,这样你可以更方便地访问和显示数据。

24.swiper #

swiper 是微信小程序中的一个视图容器组件,用于实现轮播图效果,允许用户在多个图片或内容之间进行滑动切换。它非常适合用于展示广告轮播、图片列表、产品展示等需要切换多个内容的场景。下面让我来详细讲解微信小程序中的 swiper 组件以及其相关属性和用法:

基本用法:

在小程序的 WXML 文件中,你可以使用以下方式来创建一个基本的 swiper

<swiper indicator-dots="{{true}}" autoplay="{{true}}" interval="{{3000}}">
  <swiper-item>
    <image src="image1.jpg" mode="aspectFill"></image>
  </swiper-item>
  <swiper-item>
    <image src="image2.jpg" mode="aspectFill"></image>
  </swiper-item>
  <!-- 可以添加更多的 swiper-item -->
</swiper>

上述代码创建了一个带有两个轮播项的 swiper,它会自动播放,并在每个轮播项之间切换,每次切换间隔 3 秒,并显示页码指示点。

常用属性:

以下是一些常用的 swiper 组件属性,用于配置和控制轮播效果:

swiper-item:

swiper-itemswiper 的子组件,用于包裹每个轮播项的内容。可以在其中放置图片、文本、按钮等内容。每个 swiper-item 对应一个轮播项。

事件:

swiper 组件支持 bindchange 事件,用于监听轮播项切换事件。可以在事件处理函数中获取到当前轮播项的索引。

<swiper
  indicator-dots="{{true}}"
  autoplay="{{true}}"
  interval="{{3000}}"
  bindchange="swiperChange"
>
  <swiper-item>
    <image src="image1.jpg" mode="aspectFill"></image>
  </swiper-item>
  <swiper-item>
    <image src="image2.jpg" mode="aspectFill"></image>
  </swiper-item>
</swiper>
Page({
  swiperChange(event) {
    // 获取当前轮播项的索引
    console.log("当前轮播项索引:", event.detail.current);
  },
});

注意事项:

总之,swiper 组件是在微信小程序中实现轮播图效果的强大工具,你可以根据自己的需求和设计来配置和使用它,以提供更好的用户体验。

25.mode #

<image> 组件是微信小程序中用于显示图片的组件,它提供了不同的 mode 属性,用于控制图片的显示模式。不同的 mode 属性会影响图片在组件内部的布局和展示效果。以下是常见的 mode 属性及其作用的解释:

  1. scaleToFill(默认值):

    • 图片将被拉伸或压缩以填充整个 <image> 组件,不保持原始图片的宽高比例,可能导致图片变形。
  2. aspectFit

    • 图片在保持原始宽高比的情况下尽可能大地显示在 <image> 组件内部,不会变形。
    • 图片的某一边可能会超出 <image> 组件的边界。
  3. aspectFill

    • 图片在保持原始宽高比的情况下尽可能充满 <image> 组件,可能会裁剪图片的某一部分。
    • 图片完全覆盖了 <image> 组件,不会露出空白。
  4. widthFix

    • 图片将根据 <image> 组件的宽度等比例缩放,保持原始图片的宽高比例。
    • 但图片高度不会超出 <image> 组件的高度,可能会导致图片顶部和底部有空白。
  5. top

    • 图片在 <image> 组件的顶部居中显示,不会裁剪图片。
    • 如果图片的宽高比与 <image> 组件不一致,可能会导致左右有空白。
  6. bottom

    • 图片在 <image> 组件的底部居中显示,不会裁剪图片。
    • 如果图片的宽高比与 <image> 组件不一致,可能会导致左右有空白。
  7. center

    • 图片在 <image> 组件的中间居中显示,不会裁剪图片。
    • 如果图片的宽高比与 <image> 组件不一致,可能会导致上下或左右有空白。
  8. left

    • 图片在 <image> 组件的左侧居中显示,不会裁剪图片。
    • 如果图片的宽高比与 <image> 组件不一致,可能会导致上下有空白。
  9. right

    • 图片在 <image> 组件的右侧居中显示,不会裁剪图片。
    • 如果图片的宽高比与 <image> 组件不一致,可能会导致上下有空白。

这些不同的 mode 属性允许你根据实际需求控制图片在 <image> 组件内的展示效果,以获得最佳的用户体验。根据图片的尺寸和 <image> 组件的大小,选择适当的 mode 属性,以使图片能够在页面中得到正确的布局和展示效果。

26.externalClasses #

在微信小程序中,可以使用 Component 函数来定义自定义组件,该函数接受一个配置对象作为参数,其中包含了自定义组件的属性、数据、方法等信息。

让我们逐个解释这个配置对象中的属性和选项:

  1. externalClasses: ["space"]

    • externalClasses 是一个配置项,它用于指定可以在组件外部样式中使用的 CSS 类名。在这个例子中,指定了一个外部样式类名 "space"。
    • 这意味着,开发者在使用这个自定义组件时,可以在外部样式表中定义一个名为 "space" 的样式类,然后将其应用到组件的某些元素上,从而改变组件内部元素的样式。
    • 这个功能允许开发者在自定义组件内部定义样式,然后在组件外部通过 CSS 类名来自定义组件的外观。
  2. properties

    • properties 是一个配置项,用于定义组件的属性(也称为组件的属性字段)。
    • 在这个例子中,定义了一个名为 "article" 的属性,它是一个对象类型,初始值为空对象 {}
    • 这个属性可以通过组件的属性传递方式来传递数据给自定义组件,然后在组件内部使用。
    • 开发者在使用该自定义组件时,可以通过传递不同的 "article" 对象来自定义组件的行为和显示内容。
  3. data

    • data 是一个配置项,用于定义组件内部的数据。
    • 在这个例子中,没有定义任何初始数据,因此 data 为空对象 {}
    • 开发者可以在组件内部的方法中操作这些数据,实现组件内部的逻辑和状态管理。
  4. methods

    • methods 是一个配置项,用于定义组件内部的方法。
    • 在这个例子中,没有定义任何方法,因此 methods 为空对象 {}
    • 开发者可以在这里定义自定义组件内部的方法,以处理用户交互、事件响应等逻辑。

27.showNavigationBarLoading #

showNavigationBarLoadinghideNavigationBarLoading 是微信小程序提供的用于显示和隐藏导航栏加载动画的方法,它们通常用于指示页面正在进行一些耗时的操作,以提醒用户等待。

  1. showNavigationBarLoading 方法:

    • showNavigationBarLoading 是一个微信小程序的全局方法,用于在导航栏右侧显示加载动画。
    • 当你的小程序页面执行一些需要时间较长的操作,比如网络请求或其他耗时操作时,你可以调用这个方法,以告知用户当前页面正在加载数据或执行操作。
    • 使用方式如下:

      wx.showNavigationBarLoading();
      
  2. hideNavigationBarLoading 方法:

    • hideNavigationBarLoading 也是一个微信小程序的全局方法,用于隐藏导航栏的加载动画。
    • 当你的页面加载完成或耗时操作完成后,你可以调用这个方法,以隐藏导航栏的加载动画。
    • 使用方式如下:

      wx.hideNavigationBarLoading();
      

这两个方法通常成对使用,showNavigationBarLoading 用于开始加载数据或执行耗时操作时显示加载动画,然后在数据加载完成或操作执行完毕后使用 hideNavigationBarLoading 来隐藏加载动画。

示例:

Page({
  onLoad() {
    // 在页面加载时显示导航栏加载动画
    wx.showNavigationBarLoading();

    // 模拟一个耗时操作,比如发送网络请求
    setTimeout(() => {
      // 数据加载完成后隐藏导航栏加载动画
      wx.hideNavigationBarLoading();
    }, 2000); // 假设加载数据需要2秒
  },
});

这样,在页面加载时,用户会看到导航栏右侧的加载动画,以提示页面正在加载中,当数据加载完成后,加载动画将被隐藏。这有助于提高用户体验,让用户清楚地知道页面的状态。

28.wx.stopPullDownRefresh #

wx.stopPullDownRefresh() 是微信小程序中的一个方法,用于停止页面下拉刷新的动画效果。当用户在页面顶部下拉时,如果你的页面支持下拉刷新(通过设置 enablePullDownRefreshtrue),小程序会显示一个下拉刷新的动画。一旦你的页面完成了数据的刷新操作,你可以调用 wx.stopPullDownRefresh() 来停止这个下拉刷新的动画。

以下是 wx.stopPullDownRefresh() 方法的一些关键点:

示例代码:

Page({
  onPullDownRefresh() {
    // 下拉刷新回调函数,通常用于重新加载数据
    // 在数据加载完成后调用 wx.stopPullDownRefresh() 停止刷新动画
    fetchData()
      .then(() => {
        // 数据加载成功后停止刷新动画
        wx.stopPullDownRefresh();
      })
      .catch(() => {
        // 数据加载失败也需要停止刷新动画
        wx.stopPullDownRefresh();
      });
  },
});

在上述示例中,onPullDownRefresh 是下拉刷新的回调函数,当用户下拉页面时触发。在数据加载完成后,无论成功或失败,都要调用 wx.stopPullDownRefresh() 来停止刷新动画,以告诉用户刷新操作已经完成。这有助于提供更好的用户体验,让用户知道何时可以继续浏览页面。

29.triggerEvent #

triggerEvent 是微信小程序中的一个方法,用于触发自定义组件上的事件。它允许在自定义组件内部的某个方法中触发一个自定义事件,然后可以在页面中监听并响应该事件。

以下是 triggerEvent 方法的基本用法和一些重要概念:

  1. 基本用法:

    // 在自定义组件内部的某个方法中触发一个自定义事件
    this.triggerEvent("myevent", { data: "event data" });
    

    上述代码中,this 表示自定义组件实例,triggerEvent 方法用于触发一个名为 "myevent" 的自定义事件,并传递一个包含数据的对象 { data: 'event data' } 给事件处理函数。

  2. 自定义组件内监听事件:

    在自定义组件的 WXML 中,可以通过 bind:myevent 来监听 "myevent" 事件,并指定事件处理函数:

    <custom-component bind:myevent="onMyEvent"></custom-component>
    

    在上述示例中,当自定义组件触发 "myevent" 事件时,会调用名为 "onMyEvent" 的事件处理函数。

  3. 页面中监听事件:

    在页面的 WXML 中,可以通过 bind:myevent 来监听自定义组件触发的事件,并指定事件处理函数:

    <custom-component bind:myevent="onMyEvent"></custom-component>
    

    在上述示例中,当自定义组件触发 "myevent" 事件时,会调用名为 "onMyEvent" 的事件处理函数,该函数需要在页面的 JavaScript 中定义。

  4. 传递数据:

    通过 triggerEvent 可以传递数据给事件处理函数。在触发事件时,可以在第二个参数中传递一个包含数据的对象。例如:

    this.triggerEvent("myevent", { message: "Hello, world!" });
    

    在事件处理函数中可以通过 event.detail 来访问传递的数据:

    onMyEvent(event) {
      console.log(event.detail.message); // 输出: "Hello, world!"
    }
    

通过以上方式,你可以在自定义组件内部的某个方法中触发自定义事件,并将数据传递给事件处理函数。这样可以实现自定义组件与页面之间的通信,让组件能够在特定的时机触发事件,页面可以监听并响应这些事件,从而实现更丰富的交互和功能。

30.wx.previewImage #

wx.previewImage 是微信小程序中的一个 API 方法,用于预览图片,支持在当前页面或新页面中打开图片预览界面,允许用户在预览界面中滑动浏览图片、缩放图片、保存图片等操作。

以下是 wx.previewImage 方法的详细讲解:

基本用法:

wx.previewImage({
  current: "", // 当前显示图片的链接,可选
  urls: [], // 需要预览的图片链接列表,必填
});

注意事项:

  1. urls 参数必须为一个包含至少一张图片链接的数组,否则 previewImage 方法会报错。

  2. 在预览界面中,用户可以通过滑动图片来切换图片,也可以通过手势缩放图片大小。

  3. 用户可以在预览界面中点击右上角的保存按钮,将图片保存到手机相册。

  4. wx.previewImage 方法可以在当前页面或新页面中打开预览界面,具体取决于当前页面的配置。如果在当前页面预览,用户可以通过手势返回上一页面;如果在新页面中预览,用户可以通过左上角的返回按钮返回上一页面。

示例:

// 在当前页面预览图片
wx.previewImage({
  current: "https://example.com/image1.jpg", // 当前显示图片的链接
  urls: [
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
    "https://example.com/image3.jpg",
  ],
});

// 在新页面预览图片
wx.navigateTo({
  url: "/pages/imagePreview/imagePreview?url=https://example.com/image1.jpg",
});

上述示例中,第一个调用 wx.previewImage 方法在当前页面预览图片,第二个通过 wx.navigateTo 在新页面中预览图片。在新页面预览时,需要在新页面中获取传递的图片链接参数,并在页面中调用 wx.previewImage 方法来触发预览操作。

wx.previewImage 方法通常用于实现图片点击预览功能,以提供更好的用户体验。用户可以在预览界面中自由浏览和操作图片,同时也可以保存图片到手机相册。

31.scroll-view #

scroll-view 是微信小程序中的一个可滚动的视图容器组件,它允许在一个固定区域内滚动显示内容。scroll-view 通常用于实现可滚动的长列表、聊天记录、详情页等场景,以便在有限的显示区域内显示大量内容。

以下是 scroll-view 组件的主要属性和用法:

基本用法:

<scroll-view scroll-x="{{true}}" <!-- 是否允许横向滚动 -->
  scroll-y="{{true}}"
  <!-- 是否允许纵向滚动 -->
  style="height: 300px;">
  <!-- scroll-view 的高度 -->
  <!-- 内容区域 -->
</scroll-view>

内容区域:

scroll-view 组件内部可以放置任何内容,例如文本、图片、列表等。当内容超过 scroll-view 的可视区域时,用户就可以通过滚动来查看内容。

<scroll-view style="height: 300px;">
  <text>这是一段文本内容</text>
  <image src="image.jpg"></image>
  <!-- 更多内容 -->
</scroll-view>

scroll-view 的滚动事件:

scroll-view 支持一些事件,用于监听滚动事件。例如,你可以使用 bindscroll 事件来监听滚动时触发的事件,获取滚动的位置信息等。

<scroll-view style="height: 300px;" bindscroll="onScrollViewScroll">
  <!-- 内容区域 -->
</scroll-view>
Page({
  onScrollViewScroll(event) {
    // 获取滚动的位置信息
    const { scrollTop, scrollLeft } = event.detail;
    console.log("scrollTop:", scrollTop);
    console.log("scrollLeft:", scrollLeft);
  },
});

scroll-view 的其他属性和方法:

总之,scroll-view 是微信小程序中用于实现可滚动视图的重要组件,它提供了丰富的配置选项和事件,可以用于实现各种滚动需求的页面。在开发小程序时,根据具体的需求和设计,可以使用 scroll-view 来展示滚动内容,提高用户体验。

32. wx.createSelectorQuery #

wx.createSelectorQuery 是微信小程序中的一个用于查询 DOM 节点信息的 API。它允许你通过选择器选择页面中的 DOM 元素,并获取这些元素的位置、大小等信息,以便进行后续的操作,例如动态布局、动画效果等。

以下是 wx.createSelectorQuery 的基本用法和常见操作:

创建选择器查询实例:

要开始使用 wx.createSelectorQuery,首先需要创建一个查询实例。通常,你可以在页面的 onLoad 生命周期中创建它:

// 在页面 onLoad 中创建选择器查询实例
Page({
  onLoad() {
    const query = wx.createSelectorQuery();
    // 继续后续操作...
  },
});

选择 DOM 元素:

使用 query.select 方法可以选择页面中的单个 DOM 元素,或者使用 query.selectAll 方法选择多个 DOM 元素。你需要传递一个选择器作为参数,以指定要选择的元素。

// 选择单个元素
query.select("#myElement").boundingClientRect();

// 选择多个元素
query.selectAll(".myElements").boundingClientRect();

查询 DOM 元素信息:

在选择了要查询的 DOM 元素后,你可以使用一系列方法来获取这些元素的信息,比如位置、大小、滚动位置等。常见的方法包括 boundingClientRectscrollOffsetfields 等。

// 获取元素的位置和大小信息
query
  .select("#myElement")
  .boundingClientRect((rect) => {
    console.log("Element Info:", rect);
  })
  .exec();

执行查询操作:

一旦设置好查询操作,你需要调用 exec 方法来执行查询。注意,exec 是一个异步操作,因此需要传递一个回调函数来处理查询结果。

// 执行查询操作并处理结果
query.exec((res) => {
  console.log("Query Result:", res);
});

示例:

以下是一个示例,演示如何使用 wx.createSelectorQuery 获取一个元素的位置和大小信息:

Page({
  onLoad() {
    const query = wx.createSelectorQuery();

    // 选择要查询的元素
    query
      .select("#myElement")
      .boundingClientRect((rect) => {
        console.log("Element Info:", rect);
      })
      .exec();
  },
});

在这个示例中,wx.createSelectorQuery 创建了一个选择器查询实例,然后使用 query.select('#myElement') 选择了一个 id 为 "myElement" 的 DOM 元素,并使用 boundingClientRect 方法获取了该元素的位置和大小信息。最后,通过 query.exec 执行查询并在回调函数中处理查询结果。

通过 wx.createSelectorQuery,你可以在小程序中方便地获取和操作 DOM 元素的信息,以实现各种交互效果和布局调整。

33. wx.getBackgroundAudioManager #

wx.getBackgroundAudioManager 是微信小程序提供的一个用于管理后台音频播放的 API。通过这个 API,你可以控制和管理后台音频的播放、暂停、停止,以及获取音频的状态信息等。

以下是一些常用的 wx.getBackgroundAudioManager 的方法和属性:

  1. 创建音频管理实例

    使用 wx.getBackgroundAudioManager 可以获取一个后台音频管理实例,通常需要在页面的 onLoad 生命周期中创建实例。

    const audioManager = wx.getBackgroundAudioManager();
    
  2. 设置音频信息

    你可以设置音频的标题、歌手、封面图等信息,这些信息将在音频播放时显示在通知栏。

    audioManager.title = "音频标题";
    audioManager.singer = "歌手名称";
    audioManager.coverImgUrl = "封面图 URL";
    
  3. 控制音频播放

    • audioManager.src:设置音频的播放源。
    • audioManager.play():播放音频。
    • audioManager.pause():暂停音频播放。
    • audioManager.stop():停止音频播放。
    • audioManager.seek(position):跳转到指定位置(单位:秒)继续播放。
  4. 音频状态事件

    • audioManager.onPlay(callback):监听音频播放事件。
    • audioManager.onPause(callback):监听音频暂停事件。
    • audioManager.onStop(callback):监听音频停止事件。
    • audioManager.onTimeUpdate(callback):监听音频播放进度更新事件。
    • audioManager.onEnded(callback):监听音频播放结束事件。
    • audioManager.onError(callback):监听音频播放错误事件。
  5. 获取音频状态信息

    • audioManager.currentTime:获取当前播放时间(单位:秒)。
    • audioManager.duration:获取音频总时长(单位:秒)。
    • audioManager.paused:获取音频是否处于暂停状态。
    • audioManager.buffered:获取音频已缓冲的时间范围。
  6. 控制音频音量和静音

    • audioManager.volume:设置音频的音量,取值范围为 0 到 1。
    • audioManager.muted:设置音频是否静音。

通过 wx.getBackgroundAudioManager,你可以在小程序中实现音频播放功能,并对音频的播放状态进行监听和控制。这对于开发音乐播放器、语音导航等应用非常有用。

34. video #

在微信小程序中,<video> 组件用于嵌入和播放视频内容。它允许你在小程序中集成视频播放功能,可以播放本地视频文件或在线视频流。以下是一些 <video> 组件的常见属性和用法:

<video
  src="{{videoSrc}}"    <!-- 视频源,可以是本地或网络链接 -->
  controls="{{true}}"   <!-- 是否显示视频控制条 -->
  autoplay="{{false}}"  <!-- 是否自动播放视频 -->
  poster="{{posterSrc}}"  <!-- 视频封面图 -->
  initial-time="{{0}}"  <!-- 初始播放时间,单位为秒 -->
  direction="{{0}}"     <!-- 视频方向,0:正常,90:逆时针旋转90度,180:旋转180度,270:逆时针旋转270度 -->
  danmu-list="{{danmuList}}"  <!-- 弹幕列表,可用于显示弹幕 -->
  danmu-btn="{{false}}"  <!-- 是否显示弹幕开关按钮 -->
  enable-danmu="{{false}}"  <!-- 是否允许发送弹幕 -->
  show-center-play-btn="{{true}}"  <!-- 是否显示中间的播放按钮 -->
  auto-pause-if-navigate="{{true}}"  <!-- 是否在页面切换时自动暂停视频 -->
  auto-pause-if-open-native="{{true}}"  <!-- 是否在跳转到原生播放器时自动暂停视频 -->
  vslide-gesture="{{false}}"  <!-- 是否启用纵向滑动手势 -->
  hslide-gesture="{{false}}"  <!-- 是否启用横向滑动手势 -->
  show-play-btn="{{true}}"  <!-- 是否显示播放按钮 -->
  show-center-play-btn="{{true}}"  <!-- 是否显示中间的播放按钮 -->
  show-mute-btn="{{true}}"  <!-- 是否显示静音按钮 -->
  show-captions-btn="{{true}}"  <!-- 是否显示字幕按钮 -->
  show-play-progress="{{true}}"  <!-- 是否显示播放进度 -->
  object-fit="{{contain}}"  <!-- 视频的缩放模式,可选值:fill, contain, cover -->
  bindplay="handleVideoPlay"  <!-- 绑定播放事件处理函数 -->
  bindpause="handleVideoPause"  <!-- 绑定暂停事件处理函数 -->
  bindended="handleVideoEnded"  <!-- 绑定视频播放结束事件处理函数 -->
  bindtimeupdate="handleVideoTimeUpdate"  <!-- 绑定播放时间更新事件处理函数 -->
/>

一些常见的用法和注意事项:

以下是一个简单的示例,展示了一个基本的 <video> 组件的使用:

<video src="{{videoSrc}}" controls autoplay poster="{{posterSrc}}" bindplay="handleVideoPlay" bindpause="handleVideoPause" bindended="handleVideoEnded"></video>
Page({
  data: {
    videoSrc: "https://example.com/sample.mp4",
    posterSrc: "https://example.com/poster.jpg",
  },

  handleVideoPlay() {
    console.log("Video started playing");
  },

  handleVideoPause() {
    console.log("Video paused");
  },

  handleVideoEnded() {
    console.log("Video ended");
  },
});

这个示例中,<video> 组件会自动播放,用户可以控制播放和暂停,并且在视频播放结束时触发相应的事件处理函数。

请注意,视频播放功能还涉及到网络请求和资源加载,因此在实际使用中需要考虑网络状态和性能优化,以提供良好的用户体验。

35. mark #

mark

在微信小程序中,mark 是一个用于识别触发事件的目标节点以及承载自定义数据的属性。这个属性的引入版本是基础库 2.7.1 以上,它可以用于标记具体触发事件的节点,并且可以在事件处理函数中获取这些信息。

下面是关于 mark 的一些主要信息和用法:

  1. 标记触发事件的目标节点

    • 当某个事件在多个节点上触发时,可以使用 mark 来识别事件的确切触发节点。这对于需要区分触发节点的情况非常有用。
  2. 自定义数据承载

    • 除了标识触发节点外,mark 还可以用于携带一些自定义数据,类似于 dataset,这些数据可以在事件处理函数中访问。
  3. 事件回调函数

    • 当事件触发时,事件冒泡路径上所有的 mark 会被合并,并返回给事件回调函数。这意味着你可以在事件处理函数中获取触发节点的信息和自定义数据。

下面是一个示例,展示了如何使用 mark 标记触发事件的节点并获取自定义数据:

<view>
  <button mark="button1" data-name="Button 1" bindtap="handleTap">Button 1</button>
  <button mark="button2" data-name="Button 2" bindtap="handleTap">Button 2</button>
</view>
Page({
  handleTap(event) {
    const mark = event.currentTarget.dataset.mark;
    const dataName = event.currentTarget.dataset.name;

    console.log(`点击了标记为 ${mark} 的按钮,按钮名称是:${dataName}`);
  },
});

在这个示例中,两个按钮都设置了 mark 属性,并附带了 data-name 自定义数据。当任何一个按钮被点击时,handleTap 事件处理函数会获取触发按钮的 mark 属性和自定义数据,从而区分触发的是哪个按钮,并获取按钮的名称。

通过 mark 属性,你可以更容易地区分事件触发源并携带自定义数据,以实现更精确和灵活的事件处理。

36. 导航方法 #

wx.reLaunchwx.navigateTowx.redirectTowx.switchTab 是微信小程序中用于页面跳转的不同方法,它们各自具有不同的特点和使用场景。下面我将解释它们的区别和适用场景:

  1. wx.reLaunch

    • wx.reLaunch 用于关闭所有页面,然后打开新页面。它会将当前页面栈清空,不保留任何页面历史记录,然后打开指定页面。
    • 使用场景:通常用于小程序的首屏,或者需要在某个操作后返回到小程序的首页。

    示例:

    wx.reLaunch({
      url: "/pages/home/home",
    });
    
  2. wx.navigateTo

    • wx.navigateTo 用于打开新页面,并保留当前页面在页面栈中,用户可以通过左上角返回按钮返回前一个页面。
    • 使用场景:用于多层级页面之间的跳转,例如从列表页进入详情页,然后返回列表页。

    示例:

    wx.navigateTo({
      url: "/pages/detail/detail?id=123",
    });
    
  3. wx.redirectTo

    • wx.redirectTo 用于关闭当前页面,然后打开新页面。它不保留当前页面在页面栈中,用户无法返回到前一个页面。
    • 使用场景:用于需要替换当前页面内容的情况,例如从登录页成功登录后,替换为用户个人信息页。

    示例:

    wx.redirectTo({
      url: "/pages/profile/profile",
    });
    
  4. wx.switchTab

    • wx.switchTab 用于跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
    • 使用场景:用于切换到底部 tabBar 导航的页面,通常是主要的几个页面,比如首页、分类、购物车、个人中心等。

    示例:

    wx.switchTab({
      url: "/pages/home/home",
    });
    

总结:

根据你的具体需求和页面导航结构,选择适合的跳转方法来实现你的功能。