普通视图

发现新文章,点击刷新页面。
昨天 — 2025年6月9日首页

安卓和ios小程序开发中的兼容性问题举例

2025年6月9日 20:10

一、布局渲染差异

1.1 时间格式解析不一致

具体表现
iOS 系统的 JavaScript 引擎只能解析YYYY/MM/DD格式的日期字符串,而安卓可以解析YYYY-MM-DD。若直接使用连字符格式,iOS 会返回Invalid Date
解决方案

javascript

// 安全的日期解析函数
function parseDate(dateStr) {
  if (!dateStr) return null;
  // 替换连字符为斜杠
  const formattedStr = dateStr.replace(/-/g, '/');
  const date = new Date(formattedStr);
  // 验证日期有效性
  return isNaN(date.getTime()) ? null : date;
}

// 使用示例
const iosSafeDate = parseDate('2024-01-31'); // iOS和安卓均能正确解析

1.2 安全区域适配

具体表现
iOS 全面屏设备(如 iPhone X 系列)底部存在安全区域,而安卓设备刘海屏 / 挖孔屏位置不一。
解决方案

css

/* 通用安全区域适配方案 */
.page-container {
  padding-bottom: env(safe-area-inset-bottom);
  padding-top: env(safe-area-inset-top);
}

/* 动态计算状态栏高度 */
.status-bar {
  height: var(--status-bar-height);
}

javascript

// 在app.js中全局设置状态栏高度
App({
  onLaunch() {
    wx.getSystemInfo({
      success: ({ statusBarHeight }) => {
        wx.setStorageSync('statusBarHeight', statusBarHeight);
      }
    });
  }
});

1.3 字体渲染差异

具体表现
iOS 默认使用 San Francisco 字体,安卓默认使用 Roboto 字体,导致相同文本显示高度不同。
解决方案

css

/* 统一字体族 */
.page-content {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
  line-height: 1.5; /* 强制统一行高 */
}

二、事件处理差异

2.1 触摸事件传递机制

具体表现
iOS 的catchtouchmove会完全阻止滚动,安卓则允许部分滚动穿透。
解决方案

html

预览

<!-- 滚动遮罩层 -->
<view class="mask" catchtouchmove="preventDefault">
  <!-- 内容 -->
</view>

javascript

// 阻止默认滚动行为
Page({
  preventDefault(e) {
    // iOS需要返回false,安卓返回true或false均可
    return false;
  }
});

2.2 长按事件差异

具体表现
iOS 的长按触发时间约为 800ms,安卓约为 500ms。
解决方案

javascript

// 自定义长按事件
Page({
  data: {
    longPressTimer: null
  },
  
  touchStart(e) {
    this.data.longPressTimer = setTimeout(() => {
      this.handleLongPress();
    }, 600); // 取中间值
  },
  
  touchEnd() {
    clearTimeout(this.data.longPressTimer);
  },
  
  handleLongPress() {
    // 长按处理逻辑
  }
});

三、API 接口差异

3.1 文件路径处理

具体表现
iOS 的文件路径区分大小写,安卓不区分。
解决方案

javascript

// 规范化文件路径
function normalizePath(path) {
  return path.toLowerCase(); // 统一转换为小写
}

// 使用示例
const filePath = normalizePath('/User/Images/IMG_001.JPG'); // iOS和安卓均能正确识别

3.2 录音格式兼容性

具体表现
iOS 默认支持 AAC 格式,安卓默认支持 AMR 格式。
解决方案

javascript

// 自适应录音格式
wx.getSystemInfo({
  success: ({ platform }) => {
    const format = platform === 'ios' ? 'aac' : 'amr';
    wx.startRecord({
      format,
      success: (res) => {
        // 处理录音文件
      }
    });
  }
});

四、系统特性差异

4.1 键盘弹起行为

具体表现
iOS 键盘弹起时页面整体上移,安卓键盘可能覆盖输入框。
解决方案

javascript

// 监听键盘事件调整布局
Page({
  onLoad() {
    wx.onKeyboardHeightChange(res => {
      this.setData({
        keyboardHeight: res.height
      });
    });
  }
});

css

/* 动态调整输入框位置 */
.input-container {
  bottom: {{keyboardHeight}}px;
  position: fixed;
}

4.2 后台运行限制

具体表现
iOS 小程序进入后台 5 分钟后会被终止,安卓可运行更长时间。
解决方案

javascript

// 缓存关键数据
App({
  onShow() {
    // 恢复数据
    const cacheData = wx.getStorageSync('backgroundData');
    if (cacheData) this.restoreState(cacheData);
  },
  
  onHide() {
    // 保存关键数据
    const state = this.collectAppState();
    wx.setStorage({
      key: 'backgroundData',
      data: state
    });
  }
});

五、缓存与存储差异

5.1 本地存储限制

具体表现
iOS 本地存储上限约 10MB,安卓约 20MB。
解决方案

javascript

// 智能存储管理
class StorageManager {
  constructor(limit = 8 * 1024 * 1024) { // 预留2MB空间
    this.limit = limit;
  }
  
  async set(key, value) {
    const data = JSON.stringify(value);
    const size = this.calculateSize(data);
    
    if (size > this.limit) {
      await this.clearOldestData(size);
    }
    
    wx.setStorageSync(key, data);
  }
  
  // 其他方法...
}

5.2 数据持久化差异

具体表现
iOS 会自动清理长时间未使用的小程序数据,安卓则不会。
解决方案

javascript

// 定期同步重要数据到服务器
function syncDataToServer() {
  const localData = wx.getStorageSync('importantData');
  wx.request({
    url: 'https://api.example.com/sync',
    method: 'POST',
    data: localData
  });
}

// 启动时检查数据完整性
App({
  onLaunch() {
    this.checkDataIntegrity();
    setInterval(syncDataToServer, 86400000); // 每天同步一次
  }
});

六、性能优化差异

6.1 列表渲染性能

具体表现
iOS 对长列表渲染更流畅,安卓在数据量大时易卡顿。
解决方案

javascript

// 使用虚拟列表组件
import VirtualList from '@vant/weapp/virtual-list';

Page({
  data: {
    list: [],
    itemHeight: 80, // 单项高度固定
    visibleRange: { start: 0, end: 20 }
  },
  
  onLoad() {
    this.initVirtualList();
  },
  
  initVirtualList() {
    // 初始化大数据列表
    const list = Array(1000).fill(0).map((_, i) => ({ id: i, text: `Item ${i}` }));
    this.setData({ list });
  }
});

6.2 图片加载策略

具体表现
iOS 支持渐进式图片加载,安卓默认不支持。
解决方案

html

预览

<!-- 使用占位图+懒加载 -->
<image 
  class="lazy-image" 
  src="{{item.placeholderUrl}}" 
  data-src="{{item.realUrl}}" 
  bindload="onImageLoad"
/>

javascript

Page({
  onImageLoad(e) {
    const { src, dataset } = e.currentTarget;
    if (src !== dataset.src) {
      this.setData({
        [`list[${dataset.index}].src`]: dataset.src
      });
    }
  }
});
昨天以前首页

管理不同权限用户的左侧菜单展示以及权限按钮的启用 / 禁用之其中一种解决方案

2025年6月7日 21:42

一、权限管理方案设计

1. 权限模型

推荐采用 RBAC(基于角色的访问控制)  模型,把用户分配到不同角色,角色再关联对应的权限。

  • 权限项:是最小的控制单元,像 view_useredit_product 这种。
  • 角色:由多个权限项组合而成,例如 admineditorviewer
  • 用户:和一个或多个角色相对应。

2. 权限数据存储

  • 前端存储:登录成功后,从后端获取权限数据,然后存到 Vuex/Pinia 或者 localStorage 中。

  • 权限数据结构示例

javascript

{
  user: { id: 1, name: "张三", role: "admin" },
  permissions: ["view_user", "edit_user", "view_product", "edit_product"]
}

二、左侧菜单动态展示

1. 菜单配置

创建一个菜单配置文件,把权限和菜单项关联起来。

javascript

// src/config/menu.js
export const menuList = [
  {
    path: "/dashboard",
    name: "Dashboard",
    icon: "dashboard",
    permission: "view_dashboard" // 访问该菜单所需权限
  },
  {
    path: "/user",
    name: "用户管理",
    icon: "user",
    permission: "view_user",
    children: [
      {
        path: "/user/list",
        name: "用户列表",
        permission: "view_user"
      },
      {
        path: "/user/add",
        name: "添加用户",
        permission: "add_user"
      }
    ]
  },
  // 其他菜单项...
];

2. 菜单组件实现

在组件里依据用户权限过滤菜单项。

vue

<!-- src/components/Sidebar.vue -->
<template>
  <div class="sidebar">
    <el-menu :default-active="activeMenu" mode="vertical">
      <template v-for="item in filteredMenu">
        <!-- 一级菜单 -->
        <el-menu-item 
          v-if="!item.children && hasPermission(item.permission)"
          :key="item.path"
          :index="item.path"
        >
          <i :class="item.icon"></i>
          <span slot="title">{{ item.name }}</span>
        </el-menu-item>
        
        <!-- 子菜单 -->
        <el-submenu 
          v-else-if="item.children && hasPermission(item.permission)"
          :key="item.path"
          :index="item.path"
        >
          <template slot="title">
            <i :class="item.icon"></i>
            <span>{{ item.name }}</span>
          </template>
          <el-menu-item
            v-for="child in item.children"
            :key="child.path"
            :index="child.path"
            v-if="hasPermission(child.permission)"
          >
            {{ child.name }}
          </el-menu-item>
        </el-submenu>
      </template>
    </el-menu>
  </div>
</template>

<script>
import { mapState } from "vuex";
import { menuList } from "@/config/menu";

export default {
  computed: {
    ...mapState(["permissions"]),
    // 过滤后的菜单项
    filteredMenu() {
      return menuList.filter(item => this.hasPermission(item.permission));
    }
  },
  methods: {
    // 权限检查方法
    hasPermission(permission) {
      // 如果没有设置权限,默认可见
      if (!permission) return true;
      // 检查用户是否拥有该权限
      return this.permissions.includes(permission);
    }
  }
};
</script>

三、权限按钮的展示与禁用

1. 自定义指令实现

借助自定义指令来控制按钮的显示和禁用状态。

javascript

// src/directives/permission.js
export const permission = {
  inserted(el, binding, vnode) {
    const { value } = binding;
    const permissions = vnode.context.$store.state.permissions;
    
    if (value) {
      // 检查是否有该权限
      const hasPermission = permissions.includes(value);
      
      if (!hasPermission) {
        // 没有权限:隐藏按钮
        el.parentNode && el.parentNode.removeChild(el);
        // 或者禁用按钮(根据需求选择)
        // el.disabled = true;
        // el.classList.add('is-disabled');
      }
    } else {
      console.error('需要指定权限标识!');
      el.parentNode && el.parentNode.removeChild(el);
    }
  }
};

2. 全局注册指令

在 main.js 里全局注册这个指令。

javascript

// src/main.js
import Vue from "vue";
import { permission } from "./directives/permission";

Vue.directive("permission", permission);

3. 在组件中使用

vue

<!-- 使用示例 -->
<template>
  <div>
    <!-- 有权限时显示 -->
    <el-button 
      v-permission="'add_user'"
      type="primary"
      @click="addUser"
    >
      添加用户
    </el-button>
    
    <!-- 无权限时禁用 -->
    <el-button 
      :disabled="!hasPermission('edit_user')"
      type="success"
      @click="editUser"
    >
      编辑用户
    </el-button>
  </div>
</template>

<script>
export default {
  methods: {
    hasPermission(permission) {
      return this.$store.state.permissions.includes(permission);
    }
  }
};
</script>

四、路由权限控制

对路由访问权限进行控制,防止用户手动输入 URL 访问受限页面。

javascript

// src/router/index.js
import router from "./router";
import store from "./store";

router.beforeEach((to, from, next) => {
  // 获取用户权限
  const permissions = store.state.permissions;
  
  // 检查路由是否需要权限
  if (to.meta.permission) {
    if (permissions.includes(to.meta.permission)) {
      next(); // 有权限,放行
    } else {
      next({ path: "/403" }); // 无权限,跳转到403页面
    }
  } else {
    next(); // 无需权限,直接放行
  }
});

五、权限管理流程

  1. 用户登录:用户输入账号密码登录系统。
  2. 权限验证:后端验证用户身份,返回用户角色和权限信息。
  3. 权限存储:前端把权限信息存到 Vuex/Pinia 或者 localStorage 中。
  4. 菜单渲染:根据用户权限动态渲染左侧菜单。
  5. 按钮控制:在组件里通过自定义指令或者方法控制按钮的显示和禁用。
  6. 路由拦截:对路由进行拦截,防止未授权访问。

六、优缺点分析

优点

  • 可扩展性强:能够轻松添加新的角色和权限,而不用修改大量代码。
  • 维护便捷:权限配置集中管理,降低了维护成本。
  • 安全性高:从菜单、按钮、路由三个层面进行权限控制,有效防止越权访问。

缺点

  • 初期配置复杂:需要设计合理的权限模型和数据结构。
  • 性能影响:在复杂应用中,频繁的权限检查可能会对性能产生一定影响。

都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理

2025年6月6日 21:56

一、Vue 的响应式原理

(一)Vue 2 中的响应式实现

Vue 2 使用Object.defineProperty来实现数据的响应式。当我们将一个普通的 JavaScript 对象传给 Vue 实例的data选项时,Vue 会遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转换为getter/setter。例如:

let data = {
  message: 'Hello, Vue!'
};
Object.keys(data).forEach(key => {
  let value = data[key];
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get() {
      console.log(`Getting ${key}: ${value}`);
      return value;
    },
    set(newValue) {
      if (newValue!== value) {
        value = newValue;
        console.log(`Setting ${key} to: ${newValue}`);
        // 这里触发视图更新的相关逻辑
      }
    }
  });
});

在上述代码中,通过Object.defineProperty为data对象的message属性添加了getter和setter。当获取message属性时,会触发getter,打印相关信息;当设置message属性时,会触发setter,在setter中判断新值与旧值是否不同,若不同则更新值并可以触发视图更新的逻辑(实际 Vue 中更为复杂,这里仅作示意)。

(二)Vue 3 中的响应式实现

Vue 3 改用Proxy来实现响应式。Proxy可以直接监听对象上属性的添加和删除,弥补了Object.defineProperty的一些不足。示例如下:

let data = {
  message: 'Hello, Vue 3!'
};
let handler = {
  get(target, key) {
    console.log(`Getting ${key}: ${target[key]}`);
    return target[key];
  },
  set(target, key, value) {
    if (target[key]!== value) {
      target[key] = value;
      console.log(`Setting ${key} to: ${value}`);
      // 这里触发视图更新的相关逻辑
      return true;
    }
    return false;
  }
};
let proxy = new Proxy(data, handler);

在这个例子中,通过Proxy创建了一个代理对象proxy,对data对象的属性访问和设置进行了拦截。当获取或设置proxy对象的属性时,会执行handler中的get和set方法,同样可以在set方法中触发视图更新逻辑。

(三)依赖收集与更新

无论是 Vue 2 还是 Vue 3,响应式系统的核心都包括依赖收集和更新。当数据被访问时,会进行依赖收集,将依赖该数据的 “观察者”(Watcher)添加到一个依赖容器(如 Vue 2 中的Dep)中。当数据发生变化时,依赖容器会通知所有相关的Watcher进行更新,从而重新渲染视图。例如在 Vue 2 中,每个组件实例都对应一个Watcher实例,它会在组件渲染的过程中把 “接触” 过的数据属性记录为依赖。之后当依赖项的setter触发时,会通知Watcher,使它关联的组件重新渲染。

二、Vue 的双向绑定原理

(一)双向绑定的概念

双向绑定的核心是让数据的变化自动反映到视图上,而视图的变化也能自动同步回数据。它主要应用于表单元素(如、等)。Vue 的双向绑定原理建立在响应式系统和事件监听机制上。

(二)实现流程

以为例,双向绑定的实现流程如下:

  1. 初始化绑定:当 Vue 实例被创建时,会对data选项中的message数据进行响应式处理,为其设置getter和setter。v-model指令会将message的初始值绑定到元素中,使视图显示message的当前值。
  1. 数据变化更新视图:当message的值发生改变时,Vue 的响应式系统会触发message的setter。setter通知依赖于message的Watcher去更新视图,Watcher会重新渲染依赖message的 DOM 元素,使显示的新值与数据同步。
  1. 视图变化更新数据:在使用v-model时,Vue 会为元素添加一个input事件监听器。当用户在输入框中输入内容时,会触发input事件,Vue 捕获到这个事件后,将输入框的值赋给message,触发message的setter,数据会被更新为用户输入的内容。由于数据被更新,Vue 会再次触发响应式更新过程,如果有其他依赖于message的 DOM 元素或计算属性,它们也会同步更新。

三、v-model 的实现原理

(一)v-model 是语法糖

v-model本质上是一个语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行特殊处理。它会忽略所有表单元素的value、checked、selected attribute 的初始值,而总是将 Vue 实例的数据作为数据来源。我们应该通过 JavaScript 在组件的data选项中声明初始值。

(二)v-model 在不同表单元素上的实现

  1. text 和 textarea 元素:使用value property 和input事件。例如,等价于<input type="text" :value="message" @input="message = event.target.value">:value="message"实现了数据到视图的单向绑定,将message的值绑定到输入框的value属性上;@input="message=event.target.value">。:value="message"实现了数据到视图的单向绑定,将message的值绑定到输入框的value属性上;@input="message = event.target.value"监听输入事件,当用户输入内容时,将新的值赋给message,实现视图到数据的同步。
  1. radio 和 checkbox 元素:使用checked property 和change事件。对于多个复选框,需要手动设置value值,以便在事件处理函数中拿到新数据来更新数据。而单个复选框绑定的是布尔值,在其事件处理函数中可以直接拿到新数据进行更新。例如:
<input type="checkbox" v-model="isChecked">
<input type="checkbox" v-model="checkboxValues" value="option1">
<input type="checkbox" v-model="checkboxValues" value="option2">
  1. select 字段:将value作为 prop 并将change作为事件。例如:
<select v-model="selectedOption">
  <option value="option1">Option 1</option>
  <option value="option2">Option 2</option>
</select>

等价于:

<select :value="selectedOption" @change="selectedOption = $event.target.value">
  <option value="option1">Option 1</option>
  <option value="option2">Option 2</option>
</select>

(三)v-model 在组件中的应用

在组件中,一个组件上的v-model默认会利用名为value的属性和名为input的事件(Vue 2),在 Vue 3 中v-model默认使用modelValue属性和update:modelValue事件来实现双向数据绑定。实现双向绑定的核心步骤如下:

  1. 父传子:数据通过父组件的props传递给子组件,子组件内将v-model拆解绑定数据。
  1. 子传父:通过自定义事件,子组件将新值传递给父组件修改。例如在 Vue 2 中:
<!-- 父组件 -->
<template>
  <MyComponent v-model="parentValue"></MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
  components: {
    MyComponent
  },
  data() {
    return {
      parentValue: ''
    };
  }
};
</script>
<!-- 子组件MyComponent.vue -->
<template>
  <input :value="value" @input="$emit('input', $event.target.value)">
</template>
<script>
export default {
  props: ['value']
};
</script>

在 Vue 3 中:

<!-- 父组件 -->
<template>
  <MyComponent v-model="parentValue"></MyComponent>
</template>
<script setup>
import MyComponent from './MyComponent.vue';
let parentValue = ref('');
</script>
<!-- 子组件MyComponent.vue -->
<template>
  <input :modelValue="modelValue" @update:modelValue="(newValue) => $emit('update:modelValue', newValue)">
</template>
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>

总结:

 以下是对 Vue 响应式原理、双向绑定原理及 `v-model` 实现原理的总结,帮助快速梳理核心要点:

一、响应式原理

1. Vue 2(Object.defineProperty

  • 核心机制:通过 Object.defineProperty 为数据对象的属性添加 getter/setter,拦截属性的读取和修改。
  • 依赖收集:当属性被读取时(触发 getter),收集当前组件的 Watcher 作为依赖。
  • 更新触发:当属性被修改时(触发 setter),通知所有依赖的 Watcher 重新渲染视图。
  • 局限性:无法监听数组索引和对象新增属性的变化,需通过 Vue.set 等方法手动处理。

2. Vue 3(Proxy

  • 核心机制:使用 Proxy 代理原始数据对象,拦截所有属性操作(读取、修改、删除等)。
  • 依赖收集:通过 Proxy 的 get 拦截器收集依赖(Watcher)。
  • 更新触发:通过 set 拦截器触发依赖更新,自动处理数组和对象新增属性的响应式。
  • 优势:更高效、更全面,原生支持数组和对象的所有操作。

二、双向绑定原理

核心逻辑

  • 数据 → 视图:基于响应式系统,数据变化时通过 Watcher 自动更新视图。
  • 视图 → 数据:通过监听表单元素的事件(如 inputchange),将用户输入的值同步回数据。
  • 典型场景:表单元素(如 <input><textarea><select>)的值与 Vue 实例的数据同步。

实现流程

  1. 初始化绑定:将数据初始值渲染到视图(如 :value="data")。
  2. 数据更新视图:数据变化时,响应式系统触发视图重新渲染。
  3. 视图更新数据:表单元素触发事件(如 input)时,将新值赋值给数据(如 data = $event.target.value)。

三、v-model 实现原理

1. 本质:语法糖

  • 简化表单元素的双向绑定操作,等价于 :value(或 :checked 等)与事件监听(如 @input@change)的组合。

2. 不同元素的实现

元素类型 绑定属性 监听事件 等价代码示例
input/textarea :value @input <input :value="data" @input="data=$event.target.value">
checkbox :checked @change <input :checked="data" @change="data=$event.target.checked">
radio :checked @change <input :checked="data" @change="data=$event.target.value">
select :value @change <select :value="data" @change="data=$event.target.value">

3. 组件中的 v-model

  • Vue 2:通过 props 接收父组件数据(默认属性 value),通过 $emit('input', 新值) 通知父组件更新。
  • Vue 3:通过 props 接收 modelValue,通过 $emit('update:modelValue', 新值) 实现双向绑定。

四、常见误区与注意事项

  1. 响应式边界

    • Vue 2 中,直接修改数组索引或对象新增属性不会触发更新,需用 Vue.set 或数组变异方法(如 pushsplice)。
    • Vue 3 中,Proxy 原生支持数组和对象的所有操作,无需额外处理。
  2. v-model 与单向绑定的区别

    • v-model 是双向绑定,同时包含数据到视图(:)和视图到数据(@)的逻辑。
    • 单向绑定(如 :value)仅实现数据到视图的更新,需手动添加事件监听才能反向更新数据。
  3. 组件开发注意点

    • 子组件使用 v-model 时,需显式声明 props 和触发对应事件(input 或 update:modelValue)。
    • 避免在子组件中直接修改 props 传递的数据,应通过事件通知父组件修改。

总结对比

特性 Vue 2(Object.defineProperty Vue 3(Proxy
响应式实现 getter/setter 拦截属性 Proxy 代理对象所有操作
数组 / 对象新增属性 需手动处理(Vue.set 自动支持
v-model 本质 语法糖(:value + @input 等) 同上,但组件中使用 modelValue 和 update:modelValue
❌
❌