Files
yxe-mp/packageUser/self-report/select.vue
T
weicw-dshsa 2ff8d42207 fix(自主填报): 修正重提跳转按填报类型、补 wx:key 并清理废弃 API 调用
一、填报记录「重新提交」跳错表单(用户反馈)

records.vue 原先按 activeType(当前 tab)决定跳驻店还是跑店表单,
但列表请求无守卫:切 tab 会连发请求且响应顺序不保证,
旧响应回来会盖掉当前 tab 的数据,导致 tab 与实际数据错位。
此时点「重新提交」会拿 A 表的 id 去打开 B 表的表单
(两表 id 各自从 1 开始,撞号常见,故不报错、只静默进错页面)。

改法:把填报类型钉在记录上,而不是靠 tab 状态猜
- fetchList 拉到数据后给每条记录打 reportType
- 新增 recordType(item):优先取记录自带类型,异常数据回退当前 tab
- 归一化、筛选、卡片标题/副标题、展开详情形态、重新提交跳转全部改走它
- 列表请求加自增 listRequestId 守卫,丢弃过期响应(loading 同理)

二、visit.vue 缺 wx:key 告警

小程序 wx:key 只接受简单属性路径,模板字面量写法
(:key="`store-${idx}`")会被编译器静默丢弃,四处循环因此无 key。
按项目既有 _rowKey 模式改:
- 巡店列表 :key="item._rowKey",新增 buildStoreKey/ensureStoreKeys
  (重提与草稿的旧数据无该字段,加载时补齐;提交走显式白名单,不外泄)
- 照片 :key="photo",两个 picker :key="item.id"

三、getSystemInfoSync 废弃告警

该调用在 AppNavbar 的 render(getSafeTop)里,每渲染一次告警一次。
改为挂载时取一次并缓存,优先用新接口 uni.getWindowInfo,
取不到再回退旧接口;select.vue 同源调用一并处理。

注:uview 组件库内部 u-overlay/u-popup 的 mode 传 null 告警
(u-popup 的 position computed 无 else 分支)未改,
uni_modules 已被 gitignore 且会被依赖重装覆盖,属组件库问题。
2026-10-12 13:47:03 +08:00

285 lines
6.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="select-page">
<!-- 本页 hero 即页头(设计如此),不套 AppNavbar;
navigationStyle 是 custom,状态栏高度要自己让出来,否则内容冲顶。 -->
<view class="hero" :style="{paddingTop: `${heroPaddingTop}px`}">
<view class="hero-back" @tap="back">
<u-icon name="arrow-left" size="20" color="#0f2f52"/>
</view>
<view class="hero-text">
<view class="hero-title">自主填报</view>
<view class="hero-sub">真实记录 · 及时上报 · 共创佳绩</view>
</view>
<image class="hero-avatar" :src="heroAvatar" mode="aspectFit"/>
</view>
<view class="card block">
<view class="section-title-row">
<view class="section-title">请选择填报类型</view>
</view>
<view class="section-desc">请选择您本次要填写的工作类型</view>
<view
v-for="item in typeOptions"
:key="item.value"
:class="['type-card', form.type === item.value ? 'type-card-active' : '']"
@tap="form.type = item.value"
>
<image class="type-icon" :src="item.icon" mode="aspectFit"/>
<view class="type-body">
<view class="type-title">{{ item.title }}</view>
<view class="type-desc">{{ item.desc }}</view>
</view>
<view :class="['type-radio', form.type === item.value ? 'type-radio-active' : '']">
<view v-if="form.type === item.value" class="type-radio-dot"/>
</view>
</view>
</view>
<view class="select-actions">
<view class="primary-btn confirm" @tap="confirm">确认选择</view>
</view>
<view class="select-tip">① 请选择合适的填报类型,以便更准确地记录您的工作</view>
</view>
</template>
<script>
// 顶部插画:暂无独立设计稿资源,沿用首页自主填报入口所用的同色系插图
const HERO_AVATAR = 'https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/9c6c98843c2e30247355d86ea47552aa.png'
const ICON_STORE = 'https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/0fd31838127cd57b1c1db44f2c749353.png'
const ICON_VISIT = 'https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260127/f5fbb2e4be31bcc830156f174e7931b8.png'
export default {
data() {
return {
heroAvatar: HERO_AVATAR,
// 状态栏高度:本页自绘页头,得自己避让,否则内容会顶到状态栏
statusBarHeight: 0,
form: {
type: 'store'
},
typeOptions: [
{
value: 'store',
title: '驻店填报',
desc: '适用于在门店驻店,进行商品陈列、销售等工作的场景',
icon: ICON_STORE
},
{
value: 'visit',
title: '跑店填报',
desc: '适用于外出拜访门店、客户或其他工作场景',
icon: ICON_VISIT
}
]
}
},
computed: {
// 内联 padding-top 会覆盖 .hero 里 padding 简写的上边距,
// 所以这里把原有 24rpx(≈12px)一并算进去,视觉与设计稿一致。
heroPaddingTop() {
return this.statusBarHeight + 12
}
},
onLoad() {
this.statusBarHeight = this.readStatusBarHeight()
},
methods: {
// 优先用新接口,取不到时回退旧接口,避免 getSystemInfoSync 的废弃告警
readStatusBarHeight() {
try {
if (uni.getWindowInfo) {
const info = uni.getWindowInfo()
if (info && info.statusBarHeight !== undefined) return info.statusBarHeight || 0
}
} catch (error) {
// ignore:新接口不可用时走下面的回退
}
try {
const systemInfo = uni.getSystemInfoSync()
return (systemInfo && systemInfo.statusBarHeight) || 0
} catch (error) {
return 0
}
},
back() {
if (getCurrentPages().length === 1) {
uni.switchTab({url: '/pages/index/index'})
return
}
uni.navigateBack()
},
confirm() {
const type = this.form.type
const url = type === 'visit'
? '/packageUser/self-report/visit'
: '/packageUser/self-report/create'
uni.navigateTo({
url,
fail: () => {
uni.showToast({title: '页面打开失败', icon: 'none'})
}
})
}
}
}
</script>
<style lang="scss" scoped>
.select-page {
min-height: 100vh;
background: #eef8f5;
padding-bottom: 60rpx;
}
.hero {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 40rpx 36rpx;
background: linear-gradient(180deg, #d9f5ec 0%, #eef8f5 100%);
}
/* 本页没有 AppNavbar,返回入口自绘;随流排布,避免与标题重叠 */
.hero-back {
flex-shrink: 0;
margin-right: 20rpx;
width: 64rpx;
height: 64rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
.hero-text {
flex: 1;
}
.hero-title {
display: flex;
align-items: center;
font-size: 56rpx;
font-weight: 800;
color: #0f2f52;
letter-spacing: 4rpx;
}
.hero-sub {
margin-top: 14rpx;
font-size: 24rpx;
color: #5c6c84;
letter-spacing: 2rpx;
}
.hero-avatar {
width: 180rpx;
height: 150rpx;
flex-shrink: 0;
}
.block {
margin: 0 24rpx;
}
.section-title-row {
display: flex;
align-items: center;
justify-content: space-between;
}
.section-desc {
margin: -6rpx 0 24rpx;
font-size: 24rpx;
color: #9aa3b2;
}
.type-card {
display: flex;
align-items: center;
gap: 20rpx;
padding: 26rpx 22rpx;
margin-bottom: 22rpx;
border-radius: 20rpx;
border: 3rpx solid #e6eaf0;
background: #f8fafc;
}
.type-card:last-child {
margin-bottom: 0;
}
.type-card-active {
border-color: #2fc6a7;
background: #f1fbf8;
}
.type-icon {
width: 108rpx;
height: 96rpx;
flex-shrink: 0;
}
.type-body {
flex: 1;
}
.type-title {
font-size: 34rpx;
font-weight: 700;
color: #1f2937;
}
.type-desc {
margin-top: 12rpx;
font-size: 24rpx;
color: #7b8794;
line-height: 1.5;
}
.type-radio {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
border: 3rpx solid #d3d9e2;
background: #ffffff;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
}
.type-radio-active {
border-color: #2fc6a7;
}
.type-radio-dot {
width: 22rpx;
height: 22rpx;
border-radius: 50%;
background: #2fc6a7;
}
.select-actions {
margin: 48rpx 24rpx 0;
}
.confirm {
height: 92rpx;
font-size: 32rpx;
font-weight: 600;
box-shadow: 0 12rpx 26rpx rgba(47, 198, 167, 0.28);
}
.select-tip {
margin-top: 26rpx;
text-align: center;
font-size: 22rpx;
color: #9aa3b2;
}
</style>