简历描述模板
版本一:注重功能实现
负责小程序高级功能开发,封装了20+个通用组件覆盖90%业务场景。自定义导航栏支持渐变
色、毛玻璃、沉浸式等10+种效果,TabBar实现角标、红点、动画等高级特性。开发分享海
报生成器,支持多层图文混排、二维码嵌入、一键保存分享,日均生成海报10万+张。接入
实时音视频能力,实现1对1视频通话和多人会议,音视频连接成功率99.5%。开发蓝牙设备
管理模块,支持自动扫描、连接、数据传输,适配50+种蓝牙设备。
版本二:强调技术深度
主导小程序组件化架构设计,建立组件开发规范和最佳实践。深入研究小程序组件通信机制,
封装了事件总线、props/emits、provide/inject等多种通信方案。自定义导航栏使用
getMenuButtonBoundingClientRect精确适配胶囊按钮位置,支持iOS/Android差异化处理。
分享海报采用Canvas 2D API绘制,实现了文字自动换行、图片裁剪、渐变背景等复杂效果。
实时音视频基于live-pusher和live-player组件,优化了延迟、卡顿、回声等问题。蓝牙通
信实现了数据分包、校验、重传等可靠传输机制。
版本三:突出业务价值
作为小程序技术专家,推动高级功能落地和业务增长。自定义导航栏方案使品牌识别度提升
40%,用户对界面的好评率从68%提升至85%。分享海报功能上线后,分享转化率提升3倍,
新用户获取成本降低60%。实时音视频能力支撑在线咨询业务,用户满意度达92%,客服效
率提升50%。蓝牙功能拓展了智能硬件场景,设备连接成功率99%,用户投诉率下降80%。组
件库复用率达85%,新功能开发周期缩短40%。
SOP 标准回答
Q1: 自定义导航栏是怎么实现的?
标准回答:
小程序默认的导航栏样式固定,不能满足设计需求。我们需要沉浸式导航栏、渐变色背景,
只能自定义。
实现自定义导航栏主要有三步:
第一步是在app.json里设置"navigationStyle": "custom",隐藏默认导航栏。这样整个页
面就是全屏的了,包括状态栏区域。
第二步是获取系统信息,计算导航栏高度。调用wx.getSystemInfo拿到statusBarHeight(状
态栏高度),然后调用wx.getMenuButtonBoundingClientRect拿到右上角胶囊按钮的位置信
息。导航栏高度 = 胶囊bottom + 胶囊height - statusBarHeight。
第三步是开发导航栏组件。在页面顶部放一个自定义组件,高度设置成刚才计算的值。左边
是返回按钮和标题,右边留出胶囊按钮的位置。状态栏区域设置成透明或者跟导航栏同色。
有几个坑要注意:
一是iOS和Android的胶囊按钮位置不一样。iOS的胶囊按钮靠右,Android的居中偏右。我用
胶囊按钮的right值判断,动态调整导航栏布局。
二是刘海屏适配。iPhone X这些刘海屏,状态栏高度是44px,普通屏幕是20px。我用
statusBarHeight动态设置顶部padding,确保内容不被刘海遮挡。
三是返回逻辑。自定义导航栏后,默认的返回按钮没了,要自己实现。我监听返回按钮点击,
判断页面栈深度,如果只有1层就switchTab到首页,否则navigateBack。
四是标题颜色。iOS状态栏文字颜色跟随navigationBarTextStyle,但自定义导航栏后这个配
置失效了。我根据导航栏背景色深浅,动态设置标题和返回按钮的颜色,深色背景用白色文
字,浅色背景用黑色文字。
这个自定义导航栏做好后,可以实现各种酷炫效果。渐变色背景、毛玻璃效果、滚动渐变、
沉浸式布局,产品经理特别满意。用户反馈界面更漂亮了,品牌感更强了。
Q2: 分享海报是怎么生成的?
标准回答:
分享海报功能很常见,用户点击分享按钮,生成一张海报图片,保存到相册,分享到朋友圈。
我们用Canvas来实现。
具体流程是这样的:
第一步是创建Canvas。在wxml里放一个canvas组件,设置type="2d"启用新版Canvas API。
设置宽高比如750x1334,跟设计稿一致。
第二步是绘制背景。用canvas的drawImage方法绘制背景图。如果是渐变背景,用
createLinearGradient创建渐变对象,填充矩形。
第三步是绘制内容。按设计稿从上到下绘制各个元素:头像、昵称、商品图、商品名称、价
格、二维码。用drawImage绘制图片,fillText绘制文字。
文字绘制有个坑,Canvas不支持自动换行,长文本会超出边界。我写了个函数,根据Canvas
宽度和字体大小,自动计算每行能放多少字,手动分行绘制。
图片要先下载到本地。调用wx.getImageInfo下载网络图片,拿到本地临时路径,再用
drawImage绘制。如果图片加载失败,用默认图兜底。
二维码用wx.request调用后端接口生成,返回base64或者URL,下载后绘制到Canvas上。位
置通常是右下角,大小根据设计稿调整。
第四步是导出图片。调用canvas.toTempFilePath把Canvas内容导出成临时文件,拿到文件
路径。然后调用wx.saveImageToPhotosAlbum保存到相册,或者用wx.previewImage预览。
第五步是性能优化。Canvas绘制是个重操作,我做了几个优化:一是绘制前显示loading,
防止用户等待。二是绘制完成后缓存图片路径,下次直接用缓存,不用重新绘制。三是把绘
制逻辑放到Web Worker里,不阻塞主线程。
还有个细节是清晰度。直接绘制的图片在高清屏上会模糊。我把Canvas宽高乘以dpr(设备
像素比),比如在iPhone X上宽高乘以3,绘制后再缩放回原大小,这样图片就清晰了。
这个海报生成功能上线后效果很好,分享转化率提升了3倍,用户说海报很漂亮,愿意分享。
我们还做了个海报模板库,运营可以在后台配置海报样式,不用改代码,灵活性很强。
难点与亮点分析
难点一:自定义导航栏完整实现
// components/navbar/navbar.js
Component({
properties: {
title: {
type: String,
value: ''
},
bgColor: {
type: String,
value: '#ffffff'
},
textColor: {
type: String,
value: '#000000'
},
showBack: {
type: Boolean,
value: true
}
},
data: {
statusBarHeight: 0,
navBarHeight: 0,
menuButtonInfo: null
},
lifetimes: {
attached() {
this.setNavBarInfo()
}
},
methods: {
setNavBarInfo() {
const systemInfo = wx.getSystemInfoSync()
const menuButtonInfo = wx.getMenuButtonBoundingClientRect()
const statusBarHeight = systemInfo.statusBarHeight
const navBarHeight = (menuButtonInfo.top - statusBarHeight) * 2 + menuButtonInfo.height
this.setData({
statusBarHeight,
navBarHeight,
menuButtonInfo
})
},
handleBack() {
const pages = getCurrentPages()
if (pages.length === 1) {
wx.switchTab({
url: '/pages/index/index'
})
} else {
wx.navigateBack()
}
}
}
})
<!-- components/navbar/navbar.wxml -->
<view class="navbar" style="background-color: {{bgColor}}">
<view class="navbar-status" style="height: {{statusBarHeight}}px"></view>
<view class="navbar-content" style="height: {{navBarHeight}}px">
<view wx:if="{{showBack}}" class="navbar-back" bindtap="handleBack">
<text class="icon-back" style="color: {{textColor}}">返回</text>
</view>
<view class="navbar-title">
<text style="color: {{textColor}}">{{title}}</text>
</view>
<view class="navbar-placeholder" style="width: {{menuButtonInfo.width}}px"></view>
</view>
</view>
难点二:Canvas海报生成器
// utils/posterGenerator.js
class PosterGenerator {
constructor(canvas, config) {
this.canvas = canvas
this.ctx = canvas.getContext('2d')
this.config = config
this.dpr = wx.getSystemInfoSync().pixelRatio
}
async generate() {
const { width, height } = this.config
// 设置Canvas尺寸(考虑dpr)
this.canvas.width = width * this.dpr
this.canvas.height = height * this.dpr
this.ctx.scale(this.dpr, this.dpr)
// 绘制背景
await this.drawBackground()
// 绘制内容
await this.drawContent()
// 导出图片
return this.exportImage()
}
async drawBackground() {
const { background } = this.config
if (background.type === 'image') {
const image = await this.loadImage(background.url)
this.ctx.drawImage(image, 0, 0, this.config.width, this.config.height)
} else if (background.type === 'gradient') {
const gradient = this.ctx.createLinearGradient(0, 0, 0, this.config.height)
background.colors.forEach((color, index) => {
gradient.addColorStop(index / (background.colors.length - 1), color)
})
this.ctx.fillStyle = gradient
this.ctx.fillRect(0, 0, this.config.width, this.config.height)
}
}
async drawContent() {
const { elements } = this.config
for (const element of elements) {
await this.drawElement(element)
}
}
async drawElement(element) {
const { type, x, y, width, height } = element
if (type === 'image') {
const image = await this.loadImage(element.url)
if (element.circle) {
this.drawCircleImage(image, x, y, width)
} else {
this.ctx.drawImage(image, x, y, width, height)
}
} else if (type === 'text') {
this.drawText(element)
} else if (type === 'qrcode') {
await this.drawQRCode(element)
}
}
drawCircleImage(image, x, y, size) {
this.ctx.save()
this.ctx.beginPath()
this.ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2)
this.ctx.closePath()
this.ctx.clip()
this.ctx.drawImage(image, x, y, size, size)
this.ctx.restore()
}
drawText(element) {
const { x, y, text, fontSize, color, maxWidth, lineHeight } = element
this.ctx.font = `${fontSize}px sans-serif`
this.ctx.fillStyle = color
if (maxWidth) {
const lines = this.wrapText(text, maxWidth, fontSize)
lines.forEach((line, index) => {
this.ctx.fillText(line, x, y + index * lineHeight)
})
} else {
this.ctx.fillText(text, x, y)
}
}
wrapText(text, maxWidth, fontSize) {
const lines = []
let currentLine = ''
for (const char of text) {
const testLine = currentLine + char
const metrics = this.ctx.measureText(testLine)
if (metrics.width > maxWidth) {
lines.push(currentLine)
currentLine = char
} else {
currentLine = testLine
}
}
if (currentLine) {
lines.push(currentLine)
}
return lines
}
async drawQRCode(element) {
// 生成二维码(调用后端接口或使用第三方库)
const qrcodeUrl = await this.generateQRCode(element.content)
const image = await this.loadImage(qrcodeUrl)
this.ctx.drawImage(image, element.x, element.y, element.size, element.size)
}
async generateQRCode(content) {
// 调用后端接口生成二维码
const res = await wx.request({
url: '/api/qrcode/generate',
method: 'POST',
data: { content }
})
return res.data.url
}
loadImage(url) {
return new Promise((resolve, reject) => {
const image = this.canvas.createImage()
image.onload = () => resolve(image)
image.onerror = reject
image.src = url
})
}
exportImage() {
return new Promise((resolve, reject) => {
wx.canvasToTempFilePath({
canvas: this.canvas,
success: (res) => resolve(res.tempFilePath),
fail: reject
})
})
}
}
export default PosterGenerator
真实项目经验
经验一:自定义导航栏适配
我们产品要做个沉浸式的导航栏,背景色要跟页面内容融合,还要支持渐变。小程序默认导
航栏做不了,只能自定义。
第一步是把默认导航栏隐藏,在app.json设置"navigationStyle": "custom"。这样页面就
全屏了,包括状态栏区域都可以自定义。
然后遇到第一个问题,怎么知道导航栏应该多高?不同机型状态栏高度不一样,iPhone X是
44px,普通手机是20px。我调用wx.getSystemInfo拿到statusBarHeight,这个是状态栏高度。
但光有状态栏高度还不够,还要知道导航栏内容区的高度。我发现右上角胶囊按钮是固定的,
可以用它来定位。调用wx.getMenuButtonBoundingClientRect拿到胶囊按钮的top、height、
bottom。导航栏高度算法是:(胶囊bottom - 状态栏高度) * 2 + 胶囊height。这个公式在
各种机型上都准确。
第二个问题是iOS和Android胶囊按钮位置不一样。iOS的胶囊靠右,Android的偏中间一点。
我用胶囊的right值判断,iOS的right比较大,Android的小一些。根据这个动态调整导航栏
标题的位置,确保不会被胶囊遮挡。
第三个问题是返回逻辑。自定义导航栏后,默认的返回按钮没了,要自己实现。我监听返回
按钮点击,用getCurrentPages()拿到页面栈,如果只有1层说明没有上一页,就switchTab
到首页。否则navigateBack返回上一页。
还有个细节是状态栏文字颜色。iOS的状态栏文字颜色跟随navigationBarTextStyle配置,
但自定义导航栏后这个失效了。我根据导航栏背景色的亮度,自动选择黑色或白色文字。背
景色深用白色,背景色浅用黑色,保证文字清晰可见。
这个自定义导航栏做完后,设计师特别满意,说终于可以做出漂亮的沉浸式界面了。用户反
馈界面更精致了,品牌感更强了。
面试常见追问
Q: 自定义导航栏如何处理安全区域? A: 主要是statusBarHeight状态栏高度。在导航栏顶部加一个view,高度设置为statusBarHeight,确保内容不会被刘海或状态栏遮挡。iPhone X这些刘海屏,statusBarHeight是44px,普通屏幕是20px,API会自动返回正确的值。
Q: Canvas绘制海报如何保证清晰度? A: Canvas宽高要乘以设备像素比(dpr)。比如设计稿是750x1334,在iPhone X(dpr=3)上,Canvas实际宽高应该是2250x4002。绘制完成后缩放回原大小,这样图片在高清屏上就清晰了。还要注意图片资源本身要用高清图,至少@2x。
Q: 分享海报生成慢怎么优化? A: 几个方向。一是缓存,生成过的海报保存路径,下次直接用。二是图片预加载,页面onLoad时就开始下载图片,不要等用户点分享才下载。三是异步绘制,Canvas绘制放到Web Worker或者setData之后,不阻塞主线程。四是简化设计,减少绘制元素数量,能用背景图的不用Canvas绘制。
Q: 小程序组件通信有哪些方式? A: 主要四种。一是properties/emit,父子组件通信,跟Vue的props/emit类似。二是selectComponent获取组件实例,直接调用方法。三是全局事件总线,用getApp().eventBus发布订阅。四是全局状态管理,用Pinia或者Vuex。选哪种看场景,父子组件用properties/emit,跨级或兄弟组件用事件总线或状态管理。