Skip to content

小程序

小程序技巧

shell
查看当前页面路径:在小程序开发工具的左下角可以查看。
    还可以查看页面参数、场景值。

项目 ->  通用设置 -> 内存限制 填大。

响应式数据可以在AppData里查看,不用再去console重新编译了,耗费时间。

添加编译模式,不用再点好几个页面才到要调试的页面了。

授权获取位置信息

js
1、如果用户拒绝授权后,短期内调用不会出现弹窗,而是直接进入 fail 回调。如果是开发环境,请点击开发工具左侧 缓存-清除授权数据;如果是手机,请进入小程序后点击右上菜单-关于xx-右上角菜单-设置中进行权限的手动设置,或删除小程序后重新添加。

authorizegetSettingopenSetting区别和选择使用

我一直对 uni.authorize、uni.getSetting、uni.openSetting 的使用感到纠结。

js
// 获取设置
uni.getSetting

// 申请授权
authorize

// 打开授权
openSetting
js
uni.getSetting

会去获取用户已经授权的权限。成功回调的参数res是授权的列表。

uni.authorize

如果是第一次,会弹框让用户判断是否授权。
会去判断某个权限是否已经授权。已授权走成功回调,没授权走失败回调。

uni.openSetting

是打开设置,开启或关闭某个权限。

微信中在manifest.json中必须配置如下,然后在用户打开小程序时自动弹框提示用户是否授权小程序使用位置信息。
  "mp-weixin": {
    "appid": "wx58a7869155c1d656",
    "permission": {
        "scope.userLocation": {
        "desc": "你的位置信息将用于院区自动选择"
        }
   },

频繁调用 wx.getLocation 方法会失败

如题。

微信开发文档如下:

https://developers.weixin.qq.com/community/develop/doc/000aee91a98d206bc6dbe722b51801

列表组件

看似一个简单的列表,里面要涉及的东西可不少。

因为后端返回的数据都是分页数据,所以我们得做分页处理。我们还得做下拉刷新、上啦加载跟多、日期筛选等处理。

一般都是要用到 scroll-view 组件的。

先看看别人是怎么做的:

假设有一个请求列表的接口,可传 page、size、time 等参数

法一:不用分页 hook

js
// 定义数据结构
const swiperData = reactive<SwiperItemType[]>([
  {
    name: $vm.$t('待评价'),
    current:1,
    loading:true,
    isAuth:true,
    list:[],
    firstLoad:true
  },
  {
    name:$vm?.$t('i18n_gj7dtk49_1651741146226_info'),
    current:1,
    loading:true,
    isAuth:true,
    list:[],
    firstLoad: true
  }
])

// 有一个请求列表方法,index 代表请求哪个列表(SwiperItem)的数据,flag 代表请求下一页数据还是刷新(1 是下一页,2 是刷新)
async function requestEvaluateList(index, flag = 1) { 
  let requestPage // 定义请求的页码。
  if(flag === 1) { // 1,请求下一页数据;2,刷新
    // @ts-ignore
    requestPage = swiperData[index].current + 1
  } else { // 刷新
    requestPage = 1
  }

  // @ts-ignore
  const {code, data} = await apiService.get(`episode/evaluation/list`,{
    params:{
      patientRelationId: selectedPatient.value.patientRelationId,
      searchMethod: index + 1,
      current: requestPage,
      size:swiperData[index].size
    }
  })
  if(code === 0 ) { // 请求成功了
    if(flag === 1) { // 是追加数据
      swiperData[index].list.push(...data.records ?? [])
    }else { // 是刷新数据
      swiperData[index].list = data.records ?? []
    }
    // @ts-ignore
    swiperData[index].current = requestPage
    //
    swiperData[index].isAuth = true
  } else if(code === 1010103) {
    swiperData[index].isAuth = false
  }
}

法二:使用分页 hook

js
// 定义一个 state
const state = reactive({
  currentTab: 1, // 当前的 tab 索引
  listPaging: computed(() => state.currentTab === 1 ? listPaging1 : listPaging2), // 根据 currentTab 计算出使用哪个 listPaging
  ...
})

// 使用 usePaging hook。
const listPaging1 = usePaging(
  (paging) => fetchBillList(paging, { searchMethod: 1, patientRelationId: `${state.selectPatient?.patientRelationId}` }),
  {
    manual: true,
  }
)
// listPaging2 同理

// usePaging 钩子
export function usePaging<T>(
  api: (params: any) => any, // 请求列表的 api
  options?: { // 配置选项
    manual?: boolean;
    pageSize?: number;
    transformData?: (data: T) => T[];
  }
): PagingRes<T> {
  // @ts-ignore
  const paging = reactive({
    refreshing: options?.manual ?? true, // 如果不指定 manual 是 true 还是 false,则默认为 true
    isLoadMore: false, // 正在加载更多中
    noMore: false, // 没有跟多了
    dataSource: [], // 数据
    pages: { // 分页参数
      current: 1,
      size: options?.pageSize ?? 8,
    },
    isEmpty: computed(() => !paging.refreshing && paging.dataSource.length <= 0), // 没在刷新中、返回数据源长度小于等于0就为空
    code: 0,
  });

  function loadMore() {
    if (!paging.refreshing && !paging.isLoadMore && !paging.noMore) { // 如果没有正在刷新中、没有正在加载跟多、还有数据,就去请求下一页数据。
      loadData({ current: paging.pages.current + 1 }).catch(catchEmpty);
    }
  }

  async function onRefresh(cb?: Function) { // 刷新就是去请求第一页数据
    try {
      await loadData({ current: 1 });
      // 接口成功回调
      cb?.();
    } catch (e) {
      //
    }
  }

  async function loadData(params: { current: number }) {
    paging.refreshing = params.current === 1; // 如果参数的current为1,就设置刷新状态为true
    paging.isLoadMore = params.current > 1;// 如果参数的 current > 1, 就设置 正在加载更多 为 true
    try {
      const { code, data = {}, msg } = await api({ ...paging.pages, current: params.current });
      paging.code = code;
      if (isSuccess(code)) { // 成功请求
        if (data) { // 如果有数据
          const { records = [], current, pages } = data;
          if (current === 1) { // 如果请求的第一页数据就就重新赋值
            paging.dataSource = (options?.transformData ? options.transformData(records) : records) ?? [];
          } else { // 请求下一页数据就连接(有可能连接的空数组)
            // 更多
            paging.dataSource = paging.dataSource.concat((options?.transformData ? options.transformData(records) : records) ?? []);
          }
          paging.pages.current = current;    // 将请求的 current 赋值给前端的 current
          paging.noMore = current >= pages; // 当前的页数大于等于总页数,就设置 没有更多 为 true
        }
      } else {
         // TODO: [DYLAN] 就应该从 http 请求层面去控制是否要显示错误提示,因此要屏蔽这里
        // errorModal(msg);
      }
    } catch (e) {
      //
    } finally { // 最后设置 正在加载跟多、正在刷新中为 false
      paging.isLoadMore = false;
      paging.refreshing = false;
    }
  }

  !options?.manual && onRefresh();

  return { paging, loadMore, onRefresh };
}

问题

4

shell
input输入框的focus属性不能自动聚焦。触发了focus事件的打印,但是没有看见闪烁的光标

input点击不能聚焦。。

解决:开发工具上显示有问题。真机调试则没有问题。

5

shell
radio单选框的值只能是string类型吗?

好像是的

7、

shell
搜索历史搜索扩展按钮。

一开始去获取本地的搜索历史数据。

再mouted钩子里获取搜索历史节点,判断有几行,是否大于3行,得到第三行最后一个节点的索引。

然后计算截取历史数据slice(0, lastIndex)。

是否展示扩展按钮取决于搜索历史行数。

一开始历史数据是收缩状态,然后点击扩展按钮可以切换收缩、扩展状态。

添加新的历史搜索,重新触发组件的销毁重建生命周期,再 mounted 里重新计算

其实也就是组件的重建、销毁可以用 v-if 控制。

7、

mounted 和 onLoad 生命周期钩子有什么区别?onLoad 和 onShow 执行的先后顺序?

shell
onLoad先于onShow执行

1.渲染完成之前,即mounted之前
组件(父子组件都是)生命周期优先于页面生命周期;父组件,子组件直接的顺序是父组件优先于子组件。
执行过程:
父beforeCreate=>父created=>父beforeMount=>子beforeCreate=>子created=>子beforeMount=>页面onLoad=>页面onShow;
2.渲染完成时,即beforeDestroy之前
组件(父子组件都是)生命周期优先于页面生命周期;父组件,子组件直接的顺序是子组件优先于父组件。
子mounted=>父mounted=>页面onReady;
3.销毁过程:
页面生命周期优先于组件生命周期(父子组件都是);父组件,子组件直接的顺序是子组件优先于父组件
页面onUnload=>子beforeDestroy=>子destroyed=>父beforeDestroy=>父destroyed
————————————————
版权声明:本文为CSDN博主「黄不逗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45172119/article/details/120866068

9 有时候组件上写一些样式不会生效。

小程序组件不支持直接在外部写 class

https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html#外部样式类

10

shell
reactive结合Object.assign使用不会触发响应式

    const msg = reactive({})

    setTimeout(()=>{
    Object.assign(msg, {a:2})
    },2000)
    
    watch(msg,()=>{
    console.log(1231)
    })
解决:我们项目版本比较低不可以,但最新版可以。

11、

shell
有一些组件样式差不多,可以内部需要渲染的字段不同,该怎么解决呢?

插槽。

13、scroll-view 组件可以覆盖原生组件。也就是说,scroll-view 可以配合背景使用。

14、scroll-view 组件可能出现子元素显示不全的问题。

解决办法:scroll-view 是可以使用 100% 高度的,只不过当 scroll-view 的父容器里还有另外固定高度的元素时,scroll-view 的百分比高度不会考虑兄弟元素的。这时可以使用 flex 弹性盒,flex:1 1 auto 解决高度不确定问题。

15、screenHeight 和 windowHeight 区别?

js
https://blog.csdn.net/tangyuan97/article/details/103604680

1.作用域不同:screenHeight是整合手机屏幕的高度,windowHeight是webview(不包括手机通知栏、小程序标题栏和tabBar)的高度;

2.单位不同:screenHeight的单位是rpx,windowHeight的单位是px;

20、页面怎么复用?

直接跳转到当前页面。。

其实页面也可以看作是一个组件。

21、vue 的 template 模板上不能使用可选链。

然后我选择使用 lodash 的 get 方法。例如

vue
<view v-if="get(couponDetailState, 'coupon.couponUseUserName')"></view>

但是这好像会导致触发不了响应式。

22、uniapp 使用 wx 自定义组件

根或src创建wxcomponents目录,pages.json配置useComponents

27、flex 布局下省略号 css 样式不生效问题

文本的父元素需要固定宽度,所以可以使用 flex:1;width:0;配和使用

29、微信小程序模态框换行

js

wx.showModal({
      title:'提示',
      content:'第一行内容\r\n第二行内容\r\n第三行内容\r\n第四行内容',
      success:function (res) {
        if (res.confirm) {
          console.log('用户点击确定')
        }else if (res.cancel) {
          console.log('用户点击取消')
        }
      }
    })1

30、滚动条影响页面宽度

在最外层盒子加:margin-right: calc(100% - 100vw);即可。

31、1px 像素问题

高清屏幕下 1px 对应更多的物理像素,所以 1 像素边框看起来比较粗,解决方法如下

  1. 边框使用伪类选择器,或者单独的元素实现。例如底部边框
css
.box2::after {
  content: "";
  height: 1px;
  transform: scale(.5);
  width: 100%;
  position: absolute;
  left: 0;
  bottom: 0;
  background: #000;
}

和睦家医疗项目

35、关于小程序精确的高度的问题

js
// 1. 小程序的胶囊按钮到屏幕顶部的距离:
    const menuButtonRect =wx.getMenuButtonBoundingClientRect()
        menuButtonRect.top + menuButtonRect.height

38、rpx、px 互转

js
/**
 * px to rpx
 * @param {number} px
 * @returns {number}
 */
export function px2rpx(px: number) {
  const systemInfo = getApp()!.globalData!.systemInfo as UniApp.GetSystemInfoResult;
  return px * (750 / systemInfo.windowWidth);
}

/**
 * rpx to px
 * @param {number} rpx
 * @returns {number}
 */
export function rpx2px(rpx: number) {
  return rpx / (750 / uni.getSystemInfoSync().windowWidth);
}

40、插槽里写 v-if 判断,v-if 只会判断一次,数据更新视图不会更新

应该是低版本 vue 的 bug。

解决办法:在最外层包一层 view 标签,不要写 v-if 指令。

html
  <ufh-form-item-v2
    v-model="state.invoiceForm.buyerName"
    show-bottom-border
    disable-default-padding
    style="--form-text-empty-body-fw: bold"
    :label="$t('i18n_ikenc67y_1651741146223_info')"
    border-radius-type="top"
    :placeholder="$t('i18n_pr49wht2_1651741146207_info')"
    :type="FORM_ITEM_TYPE.INPUT"
    layout="vertical"
  >
    <template #labelRightIcon>
      <view v-if="state.invoiceForm.buyerType == INVOICE_TYPE_ENUM.COMPANY" @click="getInvoiceHeaderFromWechat">
        <text class="tw-text-28-42 tw-text-333333">{{ $t('i18n_ikenc67y_1651741146223_info') }}</text>
      </view>
    </template>
  </ufh-form-item-v2>

42、uniapp 获取安全区域的 bottom

shell
3.4.8.20220428-alpha

微信小程序平台 修复 uni.getSystemInfoSync() 获取的 safeAreaInsets.bottom 为负数的Bug。

另一种方法也可以获取到安全区域:

    // 手机底部的非安全区域高度
    this.globalData.safeBottom = systemInfo.screenHeight - systemInfo.safeArea.bottom;

45、view 和 text 标签里写文本,有什么区别?

text 组件适合包裹文本,有长按选择文本等属性

52、box-sizing:box-border 对 scroll-view 有用吗?

55、小程序富文本解析,原生的 rich-text 组件有很多限制,不能点击图片 , 有<等特殊字符格式会解析失败。

js
    // 避免开头为特殊字符,解析失败
    const desc = computed(() => {
      return checkupDetail.value.selectItem.commentDetail?.replaceAll('<', '<')
    })

这种方法时错误的,应该使用社区通用的小程序富文本解析组件。

可视化编辑小程序海报

可视化编辑小程序海报

利用的是Painter库。

https://github.com/lingxiaoyi/painter-custom-poster

Released under the MIT License.