feat(visit): 客户拜访页按新原型重构为多卡片表单
参照设计稿将 pages/visit/index 拆分为 4 张表单卡片 + 历史记录卡片: - 我的信息:姓名,进页面自动带出账号 realName,可编辑 - 拜访对象:类型(门店/门店经理/其他 胶囊 tab)、姓名/门店、连锁 - 拜访信息:定位信息(点击重新定位 + 经纬度副行)、拜访日期(默认当天) - 其他信息:沟通内容(500 字 + 实时计数)、拍照上传、活动小结 一并修复: - 必填星号原用 ::before 伪元素,小程序对 <text> 伪元素支持不稳,改为 显式 <text class="field-star"> - 必填标签原复用全局类名 .required,而 common.scss 中的 .required 经 @extend .pos-a 带上 position:absolute / left:-9px / top:15px,导致标签 脱离文档流堆叠到卡片左上角。改用 .is-required 并在样式处加注释说明。 配色沿用项目主题变量,未照搬设计稿颜色。历史记录展开详情增加拜访 类型与拜访日期两行。
This commit is contained in:
+391
-150
@@ -3,88 +3,195 @@
|
|||||||
<view class="page-hero">
|
<view class="page-hero">
|
||||||
<AppNavbar title="客户拜访" show-back/>
|
<AppNavbar title="客户拜访" show-back/>
|
||||||
<view class="intro-card">
|
<view class="intro-card">
|
||||||
<view class="card-title">
|
<view class="tip-icon">
|
||||||
<view class="title-bar"></view>
|
<u-icon name="info-circle-fill" size="40rpx" :color="brandColor"/>
|
||||||
<text>培训拜访</text>
|
</view>
|
||||||
|
<view class="tip-body">
|
||||||
|
<text class="tip-title">温馨提示</text>
|
||||||
|
<text class="tip-text">请如实填写本次拜访信息,系统将自动记录您的姓名,拜访对象请填写实际到访人员或门店/经理。</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="intro-text">记录培训拜访现场定位、沟通内容和照片,提交后可回看历史记录。</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 我的信息 -->
|
||||||
<view class="card block">
|
<view class="card block">
|
||||||
<view class="section-title">新增拜访</view>
|
<view class="form-card-head">
|
||||||
<view class="form">
|
<view class="head-icon">
|
||||||
<view class="form-row location-row">
|
<u-icon name="account-fill" size="32rpx" :color="brandColor"/>
|
||||||
<text>定位信息</text>
|
|
||||||
<view class="location-box">
|
|
||||||
<view class="picker-value location-text" :class="{placeholder: !visitForm.location}">
|
|
||||||
{{ visitForm.location || '定位中,或点击右侧重新定位' }}
|
|
||||||
</view>
|
|
||||||
<view class="location-meta">
|
|
||||||
<text>{{ formatCoordinate('纬度', visitForm.lat) }}</text>
|
|
||||||
<text>{{ formatCoordinate('经度', visitForm.lng) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="refresh-btn" @tap="fetchLocation(false, true)">重新定位</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<text class="head-title">我的信息</text>
|
||||||
|
</view>
|
||||||
|
<view class="field-row">
|
||||||
|
<text class="field-label is-required"><text class="field-star">*</text>姓名</text>
|
||||||
|
<view class="field-box">
|
||||||
|
<u-icon name="account" size="32rpx" color="#9aa7b8"/>
|
||||||
|
<input
|
||||||
|
v-model="visitForm.submitterName"
|
||||||
|
class="field-input"
|
||||||
|
type="text"
|
||||||
|
maxlength="30"
|
||||||
|
placeholder="请输入您的姓名"
|
||||||
|
placeholder-class="field-placeholder"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="form-row">
|
<!-- 拜访对象 -->
|
||||||
<text>连锁</text>
|
<view class="card block">
|
||||||
<view class="selector-picker" @tap="openChainPicker">
|
<view class="form-card-head">
|
||||||
<view class="picker-value" :class="{placeholder: !visitForm.chain}">
|
<view class="head-icon">
|
||||||
{{ visitForm.chain || (chainLoading ? '连锁加载中...' : '请选择连锁') }}
|
<u-icon name="account" size="32rpx" :color="brandColor"/>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="head-title">拜访对象</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="field-row">
|
||||||
|
<text class="field-label is-required"><text class="field-star">*</text>类型</text>
|
||||||
|
<view class="type-tabs">
|
||||||
|
<view
|
||||||
|
v-for="tab in visitTypeTabs"
|
||||||
|
:key="tab.value"
|
||||||
|
:class="['type-tab', visitForm.visitType === tab.value ? 'active' : '']"
|
||||||
|
@tap="visitForm.visitType = tab.value"
|
||||||
|
>
|
||||||
|
<u-icon :name="tab.icon" size="28rpx" :color="visitForm.visitType === tab.value ? brandColor : '#9aa7b8'"/>
|
||||||
|
<text>{{ tab.label }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="form-row">
|
<view class="field-row">
|
||||||
<text>拜访人员</text>
|
<text class="field-label is-required"><text class="field-star">*</text>姓名/门店</text>
|
||||||
<input v-model="visitForm.name" maxlength="30" placeholder="请输入拜访人员"/>
|
<view class="field-box">
|
||||||
|
<input
|
||||||
|
v-model="visitForm.name"
|
||||||
|
class="field-input"
|
||||||
|
type="text"
|
||||||
|
maxlength="30"
|
||||||
|
:placeholder="targetPlaceholder"
|
||||||
|
placeholder-class="field-placeholder"
|
||||||
|
/>
|
||||||
|
<u-icon name="arrow-right" size="26rpx" color="#b6c2d1"/>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="form-row textarea-row">
|
<view class="field-row">
|
||||||
<text>沟通内容</text>
|
<text class="field-label is-required"><text class="field-star">*</text>连锁</text>
|
||||||
|
<view class="field-box" @tap="openChainPicker">
|
||||||
|
<text class="field-text" :class="{placeholder: !visitForm.chain}">
|
||||||
|
{{ visitForm.chain || (chainLoading ? '连锁加载中...' : '请选择连锁') }}
|
||||||
|
</text>
|
||||||
|
<u-icon name="arrow-right" size="26rpx" color="#b6c2d1"/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 拜访信息 -->
|
||||||
|
<view class="card block">
|
||||||
|
<view class="form-card-head">
|
||||||
|
<view class="head-icon">
|
||||||
|
<u-icon name="map-fill" size="32rpx" :color="brandColor"/>
|
||||||
|
</view>
|
||||||
|
<text class="head-title">拜访信息</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="field-row">
|
||||||
|
<text class="field-label is-required"><text class="field-star">*</text>定位信息</text>
|
||||||
|
<view class="field-box" @tap="fetchLocation(false, true)">
|
||||||
|
<u-icon name="map" size="32rpx" color="#9aa7b8"/>
|
||||||
|
<text class="field-text" :class="{placeholder: !visitForm.location}">
|
||||||
|
{{ visitForm.location || '请点击获取当前位置' }}
|
||||||
|
</text>
|
||||||
|
<u-icon name="reload" size="30rpx" color="#b6c2d1"/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="visitForm.lat || visitForm.lng" class="field-meta">
|
||||||
|
<text>{{ formatCoordinate('纬度', visitForm.lat) }}</text>
|
||||||
|
<text>{{ formatCoordinate('经度', visitForm.lng) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="field-row">
|
||||||
|
<text class="field-label is-required"><text class="field-star">*</text>拜访日期</text>
|
||||||
|
<view class="field-box" @tap="showVisitDatePicker = true">
|
||||||
|
<u-icon name="calendar" size="32rpx" color="#9aa7b8"/>
|
||||||
|
<text class="field-text" :class="{placeholder: !visitForm.visitDate}">
|
||||||
|
{{ visitForm.visitDate || '请选择拜访日期' }}
|
||||||
|
</text>
|
||||||
|
<u-icon name="arrow-right" size="26rpx" color="#b6c2d1"/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 其他信息 -->
|
||||||
|
<view class="card block">
|
||||||
|
<view class="form-card-head">
|
||||||
|
<view class="head-icon">
|
||||||
|
<u-icon name="edit-pen-fill" size="32rpx" :color="brandColor"/>
|
||||||
|
</view>
|
||||||
|
<text class="head-title">其他信息</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="field-row textarea-row">
|
||||||
|
<text class="field-label is-required"><text class="field-star">*</text>沟通内容</text>
|
||||||
|
<view class="field-box textarea-box">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="visitForm.content"
|
v-model="visitForm.content"
|
||||||
class="textarea-input"
|
class="field-textarea"
|
||||||
maxlength="500"
|
:maxlength="contentMaxLength"
|
||||||
auto-height
|
placeholder="请输入沟通内容..."
|
||||||
placeholder="请输入沟通内容"
|
placeholder-class="field-placeholder"
|
||||||
/>
|
/>
|
||||||
|
<text class="counter">{{ (visitForm.content || '').length }}/{{ contentMaxLength }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="upload" @tap="uploadPhotos">
|
<view class="field-row">
|
||||||
<view>
|
<text class="field-label is-required"><text class="field-star">*</text>拍照上传</text>
|
||||||
<view class="upload-title">拍照上传</view>
|
<view class="field-box" @tap="uploadPhotos">
|
||||||
<view class="upload-meta">{{ photos.length ? `已上传 ${photos.length} 张,点击继续拍照` : '点击拍照上传,最多 9 张' }}</view>
|
<u-icon name="camera-fill" size="32rpx" color="#9aa7b8"/>
|
||||||
</view>
|
<text class="field-text" :class="{placeholder: !photos.length}">
|
||||||
<view class="chip">必填</view>
|
{{ photos.length ? `已上传 ${photos.length} 张,点击继续拍照` : '点击拍照上传,最多 9 张' }}
|
||||||
|
</text>
|
||||||
|
<u-icon name="arrow-right" size="26rpx" color="#b6c2d1"/>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
<view v-if="photos.length" class="photo-list">
|
<view v-if="photos.length" class="photo-list">
|
||||||
<view class="photo-item" v-for="(photo, index) in photos" :key="`${photo}-${index}`">
|
<view class="photo-item" v-for="(photo, index) in photos" :key="`${photo}-${index}`">
|
||||||
<image class="photo-image" :src="photo" mode="aspectFill" @tap="previewPhoto(index)"/>
|
<image class="photo-image" :src="photo" mode="aspectFill" @tap="previewPhoto(index)"/>
|
||||||
<view class="photo-remove" @tap.stop="removePhoto(index)">x</view>
|
<view class="photo-remove" @tap.stop="removePhoto(index)">x</view>
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="form-row textarea-row">
|
<view class="field-row textarea-row">
|
||||||
<text>活动小结</text>
|
<text class="field-label is-required"><text class="field-star">*</text>活动小结</text>
|
||||||
|
<view class="field-box textarea-box">
|
||||||
<textarea
|
<textarea
|
||||||
v-model="visitForm.summary"
|
v-model="visitForm.summary"
|
||||||
class="textarea-input"
|
class="field-textarea"
|
||||||
maxlength="300"
|
maxlength="300"
|
||||||
auto-height
|
|
||||||
placeholder="请输入活动小结"
|
placeholder="请输入活动小结"
|
||||||
|
placeholder-class="field-placeholder"
|
||||||
/>
|
/>
|
||||||
|
<text class="counter">{{ (visitForm.summary || '').length }}/300</text>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="primary-btn submit" @tap="submitVisit">提交拜访</view>
|
<view class="submit-wrap">
|
||||||
|
<view class="primary-btn submit" @tap="submitVisit">
|
||||||
|
<text>提交拜访</text>
|
||||||
|
<u-icon name="arrow-right" size="30rpx" color="#ffffff"/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="card block">
|
<view class="card block">
|
||||||
<view class="section-title">历史记录</view>
|
<view class="form-card-head">
|
||||||
|
<view class="head-icon">
|
||||||
|
<u-icon name="list-dot" size="32rpx" :color="brandColor"/>
|
||||||
|
</view>
|
||||||
|
<text class="head-title">历史记录</text>
|
||||||
|
</view>
|
||||||
<view v-if="loading" class="empty">加载中...</view>
|
<view v-if="loading" class="empty">加载中...</view>
|
||||||
<view v-else-if="visitHistory.length === 0" class="empty">暂无记录</view>
|
<view v-else-if="visitHistory.length === 0" class="empty">暂无记录</view>
|
||||||
<view v-else>
|
<view v-else>
|
||||||
@@ -100,6 +207,8 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="item.expanded" class="history-detail">
|
<view v-if="item.expanded" class="history-detail">
|
||||||
|
<view class="history-line">拜访类型:{{ item.visitType || '-' }}</view>
|
||||||
|
<view class="history-line">拜访日期:{{ item.visitDate || '-' }}</view>
|
||||||
<view class="history-line">定位:{{ item.location || '-' }}</view>
|
<view class="history-line">定位:{{ item.location || '-' }}</view>
|
||||||
<view class="history-line">纬度:{{ item.lat || '-' }}</view>
|
<view class="history-line">纬度:{{ item.lat || '-' }}</view>
|
||||||
<view class="history-line">经度:{{ item.lng || '-' }}</view>
|
<view class="history-line">经度:{{ item.lng || '-' }}</view>
|
||||||
@@ -133,25 +242,43 @@
|
|||||||
@cancel="closeChainPicker"
|
@cancel="closeChainPicker"
|
||||||
@close="closeChainPicker"
|
@close="closeChainPicker"
|
||||||
/>
|
/>
|
||||||
|
<u-datetime-picker
|
||||||
|
:show="showVisitDatePicker"
|
||||||
|
:value="visitDatePickerValue"
|
||||||
|
mode="date"
|
||||||
|
@confirm="onVisitDateConfirm"
|
||||||
|
@cancel="showVisitDatePicker = false"
|
||||||
|
@close="showVisitDatePicker = false"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import dayjs from "dayjs";
|
||||||
import AppNavbar from "@/components/AppNavbar.vue";
|
import AppNavbar from "@/components/AppNavbar.vue";
|
||||||
import {chooseImg, getLocationOnly} from "@/util";
|
import {chooseImg, getLocationOnly} from "@/util";
|
||||||
import {visitAddReq, visitListReq} from "@/api/visit";
|
import {visitAddReq, visitListReq} from "@/api/visit";
|
||||||
import {merchantGroupByChainReq} from "@/api/shop";
|
import {merchantGroupByChainReq} from "@/api/shop";
|
||||||
|
import {userInfoReq} from "@/api/user";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {AppNavbar},
|
components: {AppNavbar},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
brandColor: '#2fc6a7',
|
||||||
|
contentMaxLength: 500,
|
||||||
|
visitTypeTabs: [
|
||||||
|
{label: '门店', value: '门店', icon: 'home'},
|
||||||
|
{label: '门店经理', value: '门店经理', icon: 'account'},
|
||||||
|
{label: '其他', value: '其他', icon: 'more-dot-fill'}
|
||||||
|
],
|
||||||
visitForm: this.buildForm(),
|
visitForm: this.buildForm(),
|
||||||
photos: [],
|
photos: [],
|
||||||
visitHistory: [],
|
visitHistory: [],
|
||||||
chainOptions: [],
|
chainOptions: [],
|
||||||
chainLoading: false,
|
chainLoading: false,
|
||||||
showChainPicker: false,
|
showChainPicker: false,
|
||||||
|
showVisitDatePicker: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
locating: false,
|
locating: false,
|
||||||
}
|
}
|
||||||
@@ -161,13 +288,22 @@ export default {
|
|||||||
if (!this.chainOptions.length) return [0]
|
if (!this.chainOptions.length) return [0]
|
||||||
const idx = this.chainOptions.findIndex(item => String(item.id) === String(this.visitForm.chainId))
|
const idx = this.chainOptions.findIndex(item => String(item.id) === String(this.visitForm.chainId))
|
||||||
return [idx < 0 ? 0 : idx]
|
return [idx < 0 ? 0 : idx]
|
||||||
|
},
|
||||||
|
visitDatePickerValue() {
|
||||||
|
return this.visitForm.visitDate ? dayjs(this.visitForm.visitDate).valueOf() : Date.now()
|
||||||
|
},
|
||||||
|
targetPlaceholder() {
|
||||||
|
if (this.visitForm.visitType === '门店经理') return '请输入门店经理姓名'
|
||||||
|
if (this.visitForm.visitType === '其他') return '请输入拜访人员姓名'
|
||||||
|
return '请输入拜访人员姓名 / 门店名称 / 经理姓名'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onShow() {
|
async onShow() {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.fetchLocation(true),
|
this.fetchLocation(true),
|
||||||
this.fetchHistory(),
|
this.fetchHistory(),
|
||||||
this.fetchChainOptions()
|
this.fetchChainOptions(),
|
||||||
|
this.fetchSubmitterName()
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -179,10 +315,25 @@ export default {
|
|||||||
chainId: '',
|
chainId: '',
|
||||||
chain: '',
|
chain: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
visitType: '门店',
|
||||||
|
visitDate: dayjs().format('YYYY-MM-DD'),
|
||||||
|
submitterName: '',
|
||||||
content: '',
|
content: '',
|
||||||
summary: ''
|
summary: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async fetchSubmitterName() {
|
||||||
|
if (this.visitForm.submitterName) return
|
||||||
|
try {
|
||||||
|
const {data} = await userInfoReq()
|
||||||
|
const info = data || {}
|
||||||
|
const name = info.realName || info.real_name || info.nickname || info.name || ''
|
||||||
|
if (name && !this.visitForm.submitterName) {
|
||||||
|
this.visitForm.submitterName = name
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchChainOptions() {
|
async fetchChainOptions() {
|
||||||
this.chainLoading = true
|
this.chainLoading = true
|
||||||
try {
|
try {
|
||||||
@@ -212,6 +363,11 @@ export default {
|
|||||||
this.visitForm.chain = selected ? selected.text : ''
|
this.visitForm.chain = selected ? selected.text : ''
|
||||||
this.showChainPicker = false
|
this.showChainPicker = false
|
||||||
},
|
},
|
||||||
|
onVisitDateConfirm(event) {
|
||||||
|
const raw = event && event.value !== undefined ? event.value : Date.now()
|
||||||
|
this.visitForm.visitDate = dayjs(raw).format('YYYY-MM-DD')
|
||||||
|
this.showVisitDatePicker = false
|
||||||
|
},
|
||||||
formatCoordinate(label, value) {
|
formatCoordinate(label, value) {
|
||||||
if (value === '' || value === null || value === undefined) return `${label}:-`
|
if (value === '' || value === null || value === undefined) return `${label}:-`
|
||||||
return `${label}:${value}`
|
return `${label}:${value}`
|
||||||
@@ -268,9 +424,23 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async submitVisit() {
|
async submitVisit() {
|
||||||
if (!this.visitForm.lat || !this.visitForm.lng || !this.visitForm.location) {
|
if (!String(this.visitForm.submitterName || '').trim()) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请先获取定位',
|
title: '请填写您的姓名',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.visitForm.visitType) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请选择拜访对象类型',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!String(this.visitForm.name || '').trim()) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请填写拜访对象姓名/门店',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -282,7 +452,21 @@ export default {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!this.visitForm.name || !this.visitForm.content || !this.photos.length || !this.visitForm.summary) {
|
if (!this.visitForm.lat || !this.visitForm.lng || !this.visitForm.location) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请先获取定位',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.visitForm.visitDate) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请选择拜访日期',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.visitForm.content || !this.photos.length || !this.visitForm.summary) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '请填写完整信息',
|
title: '请填写完整信息',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
@@ -297,6 +481,9 @@ export default {
|
|||||||
chainId: this.visitForm.chainId,
|
chainId: this.visitForm.chainId,
|
||||||
chain: this.visitForm.chain,
|
chain: this.visitForm.chain,
|
||||||
name: this.visitForm.name,
|
name: this.visitForm.name,
|
||||||
|
visitType: this.visitForm.visitType,
|
||||||
|
visitDate: this.visitForm.visitDate,
|
||||||
|
submitterName: this.visitForm.submitterName,
|
||||||
content: this.visitForm.content,
|
content: this.visitForm.content,
|
||||||
photos: JSON.stringify(this.photos),
|
photos: JSON.stringify(this.photos),
|
||||||
summary: this.visitForm.summary
|
summary: this.visitForm.summary
|
||||||
@@ -305,16 +492,17 @@ export default {
|
|||||||
title: '提交成功',
|
title: '提交成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
const locationSnapshot = {
|
const snapshot = {
|
||||||
lat: this.visitForm.lat,
|
lat: this.visitForm.lat,
|
||||||
lng: this.visitForm.lng,
|
lng: this.visitForm.lng,
|
||||||
location: this.visitForm.location,
|
location: this.visitForm.location,
|
||||||
chainId: this.visitForm.chainId,
|
chainId: this.visitForm.chainId,
|
||||||
chain: this.visitForm.chain
|
chain: this.visitForm.chain,
|
||||||
|
submitterName: this.visitForm.submitterName
|
||||||
}
|
}
|
||||||
this.visitForm = {
|
this.visitForm = {
|
||||||
...this.buildForm(),
|
...this.buildForm(),
|
||||||
...locationSnapshot
|
...snapshot
|
||||||
}
|
}
|
||||||
this.photos = []
|
this.photos = []
|
||||||
await this.fetchHistory()
|
await this.fetchHistory()
|
||||||
@@ -362,6 +550,8 @@ export default {
|
|||||||
name: item.name || item.visitName || '',
|
name: item.name || item.visitName || '',
|
||||||
content: item.content || item.remark || '',
|
content: item.content || item.remark || '',
|
||||||
summary: item.summary || item.result || '',
|
summary: item.summary || item.result || '',
|
||||||
|
visitType: item.visitType || item.visit_type || '',
|
||||||
|
visitDate: item.visitDate || item.visit_date || '',
|
||||||
location: item.location || item.address || '',
|
location: item.location || item.address || '',
|
||||||
lat: item.lat || item.latitude || '',
|
lat: item.lat || item.latitude || '',
|
||||||
lng: item.lng || item.longitude || '',
|
lng: item.lng || item.longitude || '',
|
||||||
@@ -449,6 +639,7 @@ export default {
|
|||||||
.visit-page {
|
.visit-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
padding-bottom: 40rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-hero {
|
.page-hero {
|
||||||
@@ -457,31 +648,35 @@ export default {
|
|||||||
|
|
||||||
.intro-card {
|
.intro-card {
|
||||||
margin: 18rpx 20rpx 0;
|
margin: 18rpx 20rpx 0;
|
||||||
padding: 20rpx 18rpx;
|
padding: 22rpx 20rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.94);
|
||||||
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.08);
|
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.tip-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-body {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 12rpx;
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-title {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-bar {
|
.tip-text {
|
||||||
width: 6rpx;
|
font-size: 25rpx;
|
||||||
height: 30rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
background: #2f78ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.intro-text {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
@@ -490,124 +685,165 @@ export default {
|
|||||||
margin: 18rpx 20rpx 0;
|
margin: 18rpx 20rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form-card-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
gap: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
gap: 14rpx;
|
||||||
padding: 12rpx 0;
|
padding-bottom: 20rpx;
|
||||||
border-bottom: 1rpx solid #edf1f7;
|
margin-bottom: 6rpx;
|
||||||
|
border-bottom: 1rpx solid #eef2f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row input {
|
.head-icon {
|
||||||
flex: 1;
|
width: 56rpx;
|
||||||
text-align: right;
|
height: 56rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(47, 198, 167, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.head-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector-picker {
|
.field-row {
|
||||||
flex: 1;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 14rpx 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selector-picker .picker-value {
|
.field-row.textarea-row {
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-row {
|
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-box {
|
.field-label {
|
||||||
flex: 1;
|
flex-shrink: 0;
|
||||||
display: flex;
|
width: 158rpx;
|
||||||
flex-direction: column;
|
font-size: 28rpx;
|
||||||
align-items: flex-end;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.picker-value {
|
|
||||||
color: var(--text-main);
|
color: var(--text-main);
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-value.placeholder {
|
/* 注意:不要用 .required 作类名 —— 全局 common.scss 里的 .required
|
||||||
color: var(--text-sub);
|
(经 uni.scss 全局引入)@extend 了 .pos-a,会带上 position:absolute /
|
||||||
|
left:-9px / top:15px,导致标签脱离文档流堆到卡片左上角。 */
|
||||||
|
.field-label.is-required {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-text {
|
.field-star {
|
||||||
|
color: #ff3b30;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-box {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
min-height: 80rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea-box {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 80rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: right;
|
min-height: 180rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: var(--text-main);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-meta {
|
.field-text.placeholder,
|
||||||
|
.field-placeholder {
|
||||||
|
color: #9aa7b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #9aa7b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
gap: 4rpx;
|
gap: 4rpx;
|
||||||
|
margin: -6rpx 0 6rpx 178rpx;
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
color: var(--text-sub);
|
color: var(--text-sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-btn {
|
.type-tabs {
|
||||||
flex-shrink: 0;
|
|
||||||
margin-top: 2rpx;
|
|
||||||
padding: 8rpx 18rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
background: #eef4ff;
|
|
||||||
color: #2f78ff;
|
|
||||||
font-size: 22rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea-row {
|
|
||||||
align-items: flex-start;
|
|
||||||
padding-top: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.textarea-input {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 120rpx;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: var(--text-main);
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tab {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12rpx 0;
|
justify-content: center;
|
||||||
border-bottom: 1rpx solid #edf1f7;
|
gap: 6rpx;
|
||||||
}
|
height: 80rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
.upload-title {
|
background: #f5f7fa;
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-meta {
|
|
||||||
color: var(--text-sub);
|
color: var(--text-sub);
|
||||||
font-size: 24rpx;
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-tab.active {
|
||||||
|
background: rgba(47, 198, 167, 0.14);
|
||||||
|
color: var(--brand-primary);
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-list {
|
.photo-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 10rpx;
|
gap: 10rpx;
|
||||||
margin-top: 10rpx;
|
margin: 6rpx 0 6rpx 178rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.photo-item {
|
.photo-item {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 160rpx;
|
width: 150rpx;
|
||||||
height: 160rpx;
|
height: 150rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
@@ -635,10 +871,15 @@ export default {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.submit-wrap {
|
||||||
|
margin: 24rpx 20rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
.submit {
|
.submit {
|
||||||
margin-top: 12rpx;
|
gap: 10rpx;
|
||||||
padding: 14rpx;
|
padding: 24rpx 32rpx;
|
||||||
text-align: center;
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history {
|
.history {
|
||||||
|
|||||||
Reference in New Issue
Block a user