Component({ properties: { visible: { type: Boolean, value: false, }, customerName: { type: String, value: '', }, initialScore: { type: Number, value: 0, }, initialContent: { type: String, value: '', }, /** 是否显示展开/收起按钮 */ showExpandBtn: { type: Boolean, value: true, }, /** 是否显示评分区域 */ showRating: { type: Boolean, value: true, }, }, data: { ratingExpanded: false, // 默认收起 serviceScore: 0, // 再次服务此客户 1-5 returnScore: 0, // 再来店可能性 1-5 content: '', keyboardHeight: 0, // 键盘高度 px canSave: false, // 是否可保存 }, lifetimes: { created() { // 初始化私有缓存(不走 setData,避免触发渲染) ;(this as any)._heartContainerRect = null ;(this as any)._ballContainerRect = null }, }, observers: { visible(val: boolean) { if (val) { // 打开弹窗时重置 this.setData({ ratingExpanded: this.data.showRating && !this.data.showExpandBtn, // 只有在显示评分且无按钮时才默认展开 serviceScore: 0, returnScore: 0, content: this.data.initialContent || '', keyboardHeight: 0, canSave: false, }) // 多次重试获取容器位置信息,确保首次也能正确缓存 this._retryGetContainerRects(0) } else { // 关闭时重置键盘高度 this.setData({ keyboardHeight: 0 }) } }, // 任意评分或内容变化时重新计算 canSave 'serviceScore, returnScore, content, showRating'( serviceScore: number, returnScore: number, content: string, showRating: boolean ) { // 如果不显示评分,只需要有内容即可保存;否则需要评分和内容都有 const canSave = showRating ? serviceScore > 0 && returnScore > 0 && content.trim().length > 0 : content.trim().length > 0 this.setData({ canSave }) }, }, methods: { /** 缓存容器位置信息 */ _cacheContainerRects() { const query = this.createSelectorQuery() query.select('.rating-right-heart').boundingClientRect() query.select('.rating-right-ball').boundingClientRect() query.exec((res) => { if (res[0]) (this as any)._heartContainerRect = res[0] as WechatMiniprogram.BoundingClientRectCallbackResult if (res[1]) (this as any)._ballContainerRect = res[1] as WechatMiniprogram.BoundingClientRectCallbackResult }) }, /** 重试获取容器位置,确保首次打开也能正确缓存 */ _retryGetContainerRects(attempt: number) { const maxAttempts = 5 const delay = 50 + attempt * 50 // 50ms, 100ms, 150ms, 200ms, 250ms setTimeout(() => { this._cacheContainerRects() // 检查是否成功缓存,如果没有且还有重试次数,继续重试 const heartRect = (this as any)._heartContainerRect const ballRect = (this as any)._ballContainerRect if ((!heartRect || !ballRect) && attempt < maxAttempts - 1) { this._retryGetContainerRects(attempt + 1) } }, delay) }, /** 切换展开/收起 */ onToggleExpand() { const nextExpanded = !this.data.ratingExpanded this.setData({ ratingExpanded: nextExpanded }) // 展开后重新获取位置 if (nextExpanded) { setTimeout(() => { this._cacheContainerRects() }, 100) } }, /** 爱心点击 */ onHeartTap(e: WechatMiniprogram.BaseEvent) { const score = e.currentTarget.dataset.score as number this.setData({ serviceScore: score }) }, /** 爱心区域触摸开始 */ onHeartTouchStart(e: WechatMiniprogram.TouchEvent) { this._updateHeartScore(e) }, /** 爱心区域触摸移动 */ onHeartTouchMove(e: WechatMiniprogram.TouchEvent) { this._updateHeartScore(e) }, /** 更新爱心评分 */ _updateHeartScore(e: WechatMiniprogram.TouchEvent) { const container = (this as any)._heartContainerRect as WechatMiniprogram.BoundingClientRectCallbackResult | null if (!container) return const touch = e.touches[0] // 垂直方向超出不影响评分,仅用 X 轴计算 let relativeX = touch.clientX - container.left // 水平边界:最左=1分,最右=5分 if (relativeX < 0) relativeX = 0 if (relativeX > container.width) relativeX = container.width // 计算分数 (1-5) const score = Math.ceil((relativeX / container.width) * 5) const finalScore = Math.max(1, Math.min(5, score)) if (finalScore !== this.data.serviceScore) { this.setData({ serviceScore: finalScore }) } }, /** 台球点击 */ onBallTap(e: WechatMiniprogram.BaseEvent) { const score = e.currentTarget.dataset.score as number this.setData({ returnScore: score }) }, /** 台球区域触摸开始 */ onBallTouchStart(e: WechatMiniprogram.TouchEvent) { this._updateBallScore(e) }, /** 台球区域触摸移动 */ onBallTouchMove(e: WechatMiniprogram.TouchEvent) { this._updateBallScore(e) }, /** 更新台球评分 */ _updateBallScore(e: WechatMiniprogram.TouchEvent) { const container = (this as any)._ballContainerRect as WechatMiniprogram.BoundingClientRectCallbackResult | null if (!container) return const touch = e.touches[0] // 垂直方向超出不影响评分,仅用 X 轴计算 let relativeX = touch.clientX - container.left // 水平边界:最左=1分,最右=5分 if (relativeX < 0) relativeX = 0 if (relativeX > container.width) relativeX = container.width // 计算分数 (1-5) const score = Math.ceil((relativeX / container.width) * 5) const finalScore = Math.max(1, Math.min(5, score)) if (finalScore !== this.data.returnScore) { this.setData({ returnScore: finalScore }) } }, /** 文本输入 */ onContentInput(e: WechatMiniprogram.CustomEvent<{ value: string }>) { this.setData({ content: e.detail.value }) }, /** 键盘弹出 */ onTextareaFocus(e: WechatMiniprogram.InputEvent) { let height = (e as any).detail?.height ?? 0 // 修复:首次激活时键盘高度可能为0,需要设置最小值确保弹窗移动 if (height === 0) { height = 260 // 微信小程序默认键盘高度约 260px } this.setData({ keyboardHeight: height }) }, /** 键盘收起 */ onTextareaBlur() { this.setData({ keyboardHeight: 0 }) }, /** 确认保存 */ onConfirm() { if (!this.data.canSave) return // 计算综合评分:取两个评分的平均值,转换为10分制 const avgScore = (this.data.serviceScore + this.data.returnScore) / 2 const finalScore = avgScore * 2 // 转换为10分制 this.triggerEvent('confirm', { score: finalScore, content: this.data.content.trim(), serviceScore: this.data.serviceScore, returnScore: this.data.returnScore, }) }, /** 取消 */ onCancel() { this.triggerEvent('cancel') }, /** 阻止冒泡 */ noop() {}, }, })