Files
yxe-mp/packageUser/self-report/select.vue
T
weicw-dshsa aff1e52e3e feat(自主填报): 拆分驻店/跑店填报并新增跑店填报页
「自主填报」原本只有一种表单。本次拆成两种类型:

1. 新增选择填报类型页 packageUser/self-report/select
   - 按设计稿:hero 页头 + 两张类型卡(驻店填报 / 跑店填报)+ 确认选择
   - 入口(首页、我的)由直接进 create 改为先进 select

2. 原自主填报页改为「驻店填报」
   - 仅改导航标题,表单内容不变

3. 新增跑店填报页 packageUser/self-report/visit
   - 复制现有跑店/巡店任务(pages/task/detail 的 cateId=4 分支)的填报页并改造:
     保留巡店列表与下方内容,去掉列表上方的任务卡片/总进度/任务详情;
     打卡、摆台照、销量、小票、门店评分一并不保留
   - 巡店列表内「连锁筛选」「门店筛选」是两个并列的独立输入框,
     各自弹各自的选择器(不串联),样式与驻店填报逐项对齐;
     换连锁会清空已选门店
   - 每条巡店可新增/删除,独立获取定位 + 打卡拍照(最多 9 张)
   - 定位不强制(巡店任务里是强制的,这里放开)
   - 底部「保存草稿」+「提交填报」,草稿交互与驻店填报一致:
     进页面询问是否加载、提交成功后清除;storage key 与驻店分开
   - 驳回后重新提交复用同一页面(mode=resubmit)

4. 填报记录页加「驻店 / 跑店」类型切换
   - 两表分表查询,各自分页;跑店卡片展示门店数、巡店列表(门店/定位/照片)
   - 连锁与门店关键词筛选落到巡店列表的每一行

5. 药审核加「跑店填报」类型
   - 新增 AuditVisitReportItem,展开时按 id 拉详情(列表不下发 store_list)
   - 跑店没有销量小计,审核通过需手填奖金,故补奖金输入弹层

顺带重构:
- 门店解析(扁平列表 / 连锁嵌套两种结构的兼容、门店明细不入页面 data
  的性能处理)抽到 util/store-picker,create 与 visit 共用一份,
  避免两页各自维护导致解析口径不一致
2026-10-11 22:50:15 +08:00

269 lines
6.1 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() {
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
},
methods: {
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>