40fed38f83
非 h5 平台的 :key 只接受简单属性名,模板字面量与运算表达式会被编译器
整条丢弃(uni-template-compiler 的 getForKey:Identifier 走 *this 或原名,
MemberExpression 取属性名,其余一律 warning + 返回空串)。
所以这批告警不只是文案问题:产物里对应的 <block wx:for> 后面确实没有
wx:key,列表项因此失去复用标识,增删时会整段重建节点。本次按编译器
接受的写法逐一改正,共 13 个文件。
改法:
- 纯 URL 字符串数组(打卡/门头/小票/凭证/巡店照片)直接用元素自身
:key="img",编译为 wx:key="*this"
- 对象数组用其已有唯一字段:连锁/门店 item.id、抄送用户 user.id、
审计销量行 detail.key、编辑销量项 detail._key
- 本身无唯一字段的巡店列表补 _key:task/detail 沿用 visit.vue 已验证的
_rowKey(自增 seed,增删稳定),records 与 AuditVisitReportItem 用序号
sales.vue 另有一处隐蔽问题:row 是 {item, index} 包装对象,
:key="row.item._key || row.index" 即便写成简单形式也会生成 wx:key="_key",
在 row 上取不到值。改为在 visibleSalesItems 里把 key 提到包装对象顶层,
_key 缺失时用序号兜底。create.vue 的销量摘要同样补 __key 兜底,避免重复 key。
注:task/detail 的 storeList 提交走 sanitizeStoreList() 显式白名单,
_rowKey 不会外泄到后端。
验证:HBuilderX cli 重新编译 —— 编译成功,非 h5 平台 :key 告警 0 条
(修改前满屏 40+ 条),产物中 wx:for 与 wx:key 数量逐文件相等。
1423 lines
45 KiB
Vue
1423 lines
45 KiB
Vue
<template>
|
||
<view class="chronic-page">
|
||
<view class="page-hero">
|
||
<AppNavbar title="慢病信息收集" show-back/>
|
||
<view class="intro-card">
|
||
<view class="card-title">
|
||
<view class="title-bar"></view>
|
||
<text>慢病信息收集</text>
|
||
</view>
|
||
<text class="intro-text">收集三高信息,完善姓名、性别、电话、出生年月日、病史、用药史。</text>
|
||
<text class="intro-meta">信息仅用于慢病管理与跟进,提交后可在后台查看。</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">基础信息</view>
|
||
<view class="form">
|
||
<view class="form-row">
|
||
<text>连锁</text>
|
||
<view class="picker-value" @tap="openChainPicker">
|
||
{{ form.chainName || (merchantLoading ? '加载中...' : '请选择') }}
|
||
</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>门店</text>
|
||
<view class="picker-value" @tap="openMerchantPicker">
|
||
{{ form.merchantName || '请选择' }}
|
||
</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>姓名</text>
|
||
<input v-model="form.name" placeholder="请输入姓名"/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>性别</text>
|
||
<view class="picker-value" @tap="openGenderPicker">{{ form.gender || '请选择' }}</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>电话</text>
|
||
<input type="number" v-model="form.phone" placeholder="请输入联系方式"/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>出生年月日</text>
|
||
<view class="picker-value" @tap="openBirthPicker">{{ form.birthday || '请选择' }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">血液信息</view>
|
||
<view class="form">
|
||
<view class="ref-row">
|
||
<view class="ref-btn" @tap="showHemoglobinReference">血红蛋白参考值</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>血红蛋白数值</text>
|
||
<input v-model="form.hemoglobinValue" type="digit" placeholder="请输入血红蛋白数值"/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>是否贫血</text>
|
||
<view class="picker-value" @tap="openAnemiaPicker">{{ form.anemiaLevel || '请选择' }}</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>是否地贫</text>
|
||
<view class="picker-value" @tap="openThalassemiaPicker">{{ form.thalassemiaType || '请选择' }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-head">
|
||
<view class="section-title">用药追踪</view>
|
||
<view class="tracking-add-btn" @tap="addTrackingItem">添加</view>
|
||
</view>
|
||
<view class="tracking-tip">选择药品后填写本次购药时间和购买盒数,系统会按每盒服用天数自动计算预计服用完时间。</view>
|
||
<view v-if="trackingList.length === 0" class="tracking-empty">暂无用药追踪,可点击添加记录。</view>
|
||
<view v-for="(item, index) in trackingList" :key="item._key" class="tracking-item">
|
||
<view class="tracking-item-head">
|
||
<text>用药追踪{{ index + 1 }}</text>
|
||
<view class="tracking-remove" @tap="removeTrackingItem(index)">移除</view>
|
||
</view>
|
||
<view class="form">
|
||
<view class="form-row">
|
||
<text>药品</text>
|
||
<view class="picker-value tracking-product-value" @tap="openTrackingProductPicker(index)">
|
||
{{ item.productName || (trackingProductLoading ? '加载中...' : '请选择') }}
|
||
</view>
|
||
</view>
|
||
<view v-if="item.productName" class="tracking-product-meta">
|
||
<text>规格粒/片:{{ item.dailyUsageCount || '-' }}</text>
|
||
<text>说明书每天使用/次:{{ item.dosagePerTime || '-' }}</text>
|
||
<text>每次几粒/片:{{ item.boxUsageDays || '-' }}</text>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>本次购药时间</text>
|
||
<view class="picker-value" @tap="openTrackingDatePicker(index)">{{ item.purchaseTime || '请选择' }}</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>购买盒数</text>
|
||
<input
|
||
:value="item.boxCount"
|
||
type="number"
|
||
placeholder="请输入购买盒数"
|
||
@input="onTrackingBoxInput(index, $event)"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>预估服用完时间</text>
|
||
<view class="readonly-value">{{ item.estimatedFinishTime || '-' }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">三高信息</view>
|
||
<checkbox-group @change="setConditions">
|
||
<view class="conditions">
|
||
<label v-for="item in conditionOptions" :key="item.value" class="condition">
|
||
<checkbox :value="item.value" :checked="form[item.value] === 1" color="#2563eb"/>
|
||
<text class="condition-text">{{ item.label }}</text>
|
||
</label>
|
||
</view>
|
||
</checkbox-group>
|
||
<view v-if="form.highBloodPressure === 1" class="form pressure-form">
|
||
<view class="ref-row">
|
||
<view class="ref-btn" @tap="showHighBloodPressureReference">高血压参考值</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>收缩压</text>
|
||
<input
|
||
v-model="form.systolicPressure"
|
||
type="number"
|
||
placeholder="请输入收缩压"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>舒张压</text>
|
||
<input
|
||
v-model="form.diastolicPressure"
|
||
type="number"
|
||
placeholder="请输入舒张压"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view v-if="form.highBloodSugar === 1" class="form pressure-form">
|
||
<view class="ref-row">
|
||
<view class="ref-btn" @tap="showHighBloodSugarReference">高血糖参考值</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>空腹血糖</text>
|
||
<input
|
||
v-model="form.fastingBloodGlucose"
|
||
type="digit"
|
||
placeholder="请输入空腹血糖"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>餐后2小时血糖</text>
|
||
<input
|
||
v-model="form.postprandial2hBloodGlucose"
|
||
type="digit"
|
||
placeholder="请输入餐后2小时血糖"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>糖化血红蛋白(HbA1c)</text>
|
||
<input
|
||
v-model="form.hba1c"
|
||
type="digit"
|
||
placeholder="请输入HbA1c"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view v-if="form.hyperlipidemia === 1" class="form pressure-form">
|
||
<view class="ref-row">
|
||
<view class="ref-btn" @tap="showHyperlipidemiaReference">高血脂参考值</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>总胆固醇(TC)</text>
|
||
<input
|
||
v-model="form.totalCholesterol"
|
||
type="digit"
|
||
placeholder="请输入TC"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>甘油三酯(TG)</text>
|
||
<input
|
||
v-model="form.triglyceride"
|
||
type="digit"
|
||
placeholder="请输入TG"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>低密度脂蛋白(LDL-C)(“坏”胆固醇)</text>
|
||
<input
|
||
v-model="form.ldlC"
|
||
type="digit"
|
||
placeholder="请输入LDL-C"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<text>高密度脂蛋白(HDL-C)(“好”胆固醇)</text>
|
||
<input
|
||
v-model="form.hdlC"
|
||
type="digit"
|
||
placeholder="请输入HDL-C"
|
||
/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">病史</view>
|
||
<textarea class="textarea" v-model="form.medicalHistory" placeholder="请输入既往病史"></textarea>
|
||
</view>
|
||
|
||
<view class="card block">
|
||
<view class="section-title">用药史</view>
|
||
<textarea class="textarea" v-model="form.medicationHistory" placeholder="请输入当前/既往用药情况"></textarea>
|
||
</view>
|
||
|
||
<view class="primary-btn submit-btn" @tap="submitForm">提交信息</view>
|
||
|
||
<u-picker
|
||
:show="showGenderPicker"
|
||
:columns="[genderOptions]"
|
||
@confirm="onGenderConfirm"
|
||
@cancel="closeGenderPicker"
|
||
@close="closeGenderPicker"
|
||
/>
|
||
<u-picker
|
||
:show="showAnemiaPicker"
|
||
:columns="[anemiaOptions]"
|
||
@confirm="onAnemiaConfirm"
|
||
@cancel="closeAnemiaPicker"
|
||
@close="closeAnemiaPicker"
|
||
/>
|
||
<u-picker
|
||
:show="showThalassemiaPicker"
|
||
:columns="[thalassemiaOptions]"
|
||
@confirm="onThalassemiaConfirm"
|
||
@cancel="closeThalassemiaPicker"
|
||
@close="closeThalassemiaPicker"
|
||
/>
|
||
<u-datetime-picker
|
||
:show="showBirthPicker"
|
||
:value="birthPickerValue"
|
||
:min-date="minDate"
|
||
mode="date"
|
||
@confirm="onBirthConfirm"
|
||
@cancel="closeBirthPicker"
|
||
@close="closeBirthPicker"
|
||
/>
|
||
<u-picker
|
||
:show="showChainPicker"
|
||
:columns="[chainOptions]"
|
||
:loading="merchantLoading"
|
||
:defaultIndex="chainPickerIndex"
|
||
keyName="text"
|
||
@confirm="onChainConfirm"
|
||
@cancel="closeChainPicker"
|
||
@close="closeChainPicker"
|
||
/>
|
||
<u-popup :show="showMerchantPicker" mode="bottom" round="24rpx" @close="closeMerchantPicker">
|
||
<view class="merchant-picker">
|
||
<view class="merchant-picker-head">
|
||
<view class="picker-action" @tap="closeMerchantPicker">取消</view>
|
||
<text class="picker-title">选择门店</text>
|
||
<view class="picker-action primary" @tap="confirmMerchantPicker">确定</view>
|
||
</view>
|
||
<view class="merchant-picker-search">
|
||
<input
|
||
v-model="merchantSearchKeyword"
|
||
class="merchant-search-input"
|
||
placeholder="搜索门店"
|
||
placeholder-class="merchant-search-placeholder"
|
||
/>
|
||
</view>
|
||
<view v-if="merchantLoading" class="merchant-picker-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<picker-view
|
||
v-else
|
||
class="merchant-picker-view"
|
||
:value="merchantPickerValue"
|
||
:indicator-style="merchantIndicatorStyle"
|
||
@change="onMerchantPickerChange"
|
||
>
|
||
<picker-view-column>
|
||
<view
|
||
v-for="(item, idx) in merchantFilteredOptions"
|
||
:key="item.id"
|
||
class="merchant-picker-item"
|
||
>
|
||
{{ item.text }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
</u-popup>
|
||
<u-popup :show="showTrackingProductPicker" mode="bottom" round="24rpx" @close="closeTrackingProductPicker">
|
||
<view class="tracking-product-picker">
|
||
<view class="merchant-picker-head">
|
||
<view class="picker-action" @tap="closeTrackingProductPicker">取消</view>
|
||
<text class="picker-title">选择药品</text>
|
||
<view class="picker-action primary" @tap="closeTrackingProductPicker">完成</view>
|
||
</view>
|
||
<view class="merchant-picker-search">
|
||
<input
|
||
v-model="trackingProductKeyword"
|
||
class="merchant-search-input"
|
||
placeholder="搜索药品名称/规格粒片/使用次数"
|
||
placeholder-class="merchant-search-placeholder"
|
||
/>
|
||
</view>
|
||
<view v-if="trackingProductLoading" class="merchant-picker-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<scroll-view v-else class="tracking-product-scroll" scroll-y>
|
||
<view v-if="filteredTrackingProducts.length === 0" class="tracking-empty">暂无药品</view>
|
||
<view
|
||
v-for="(item, idx) in filteredTrackingProducts"
|
||
:key="idx"
|
||
class="tracking-product-option"
|
||
@tap="selectTrackingProduct(idx)"
|
||
>
|
||
<view class="tracking-product-name">{{ item.productName || '-' }}</view>
|
||
<view class="tracking-product-option-meta">规格粒/片:{{ item.dailyUsageCount || '-' }}</view>
|
||
<view class="tracking-product-option-meta">说明书每天使用/次:{{ item.dosagePerTime || '-' }}</view>
|
||
<view class="tracking-product-option-meta">每次几粒/片:{{ item.boxUsageDays || '-' }}</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</u-popup>
|
||
<u-datetime-picker
|
||
:show="showTrackingDatePicker"
|
||
:value="trackingDatePickerValue"
|
||
:min-date="minDate"
|
||
mode="date"
|
||
@confirm="onTrackingDateConfirm"
|
||
@cancel="closeTrackingDatePicker"
|
||
@close="closeTrackingDatePicker"
|
||
/>
|
||
<u-popup
|
||
:show="showReferencePopup"
|
||
mode="center"
|
||
round="16"
|
||
closeable
|
||
@close="closeReferencePopup"
|
||
>
|
||
<view class="reference-popup">
|
||
<view class="reference-title">{{ referencePopupTitle }}</view>
|
||
<scroll-view scroll-y class="reference-content">
|
||
<text class="reference-text">{{ referencePopupContent }}</text>
|
||
</scroll-view>
|
||
<view class="reference-confirm" @tap="closeReferencePopup">我知道了</view>
|
||
</view>
|
||
</u-popup>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {chronicDiseaseCollectionAddReq} from "@/api/reimburse";
|
||
import dayjs from "dayjs";
|
||
import AppNavbar from "@/components/AppNavbar.vue";
|
||
import {merchantGroupByChainReq} from "@/api/shop";
|
||
import {chronicDiseaseProductListReq} from "@/api/product";
|
||
|
||
export default {
|
||
components: {AppNavbar},
|
||
data() {
|
||
return {
|
||
genderOptions: ['男', '女', '其他'],
|
||
anemiaOptions: ['否', '轻度', '中度', '重度'],
|
||
thalassemiaOptions: ['否', '轻型', '中间型', '重型'],
|
||
conditionOptions: [
|
||
{label: '高血压', value: 'highBloodPressure'},
|
||
{label: '高血糖', value: 'highBloodSugar'},
|
||
{label: '高血脂', value: 'hyperlipidemia'}
|
||
],
|
||
form: {
|
||
chainId: '',
|
||
chainName: '',
|
||
merchantId: '',
|
||
merchantName: '',
|
||
name: '',
|
||
gender: '',
|
||
phone: '',
|
||
birthday: '',
|
||
hemoglobinValue: '',
|
||
anemiaLevel: '否',
|
||
thalassemiaType: '否',
|
||
highBloodPressure: 0,
|
||
highBloodPressureValue: '',
|
||
systolicPressure: '',
|
||
diastolicPressure: '',
|
||
highBloodSugar: 0,
|
||
fastingBloodGlucose: '',
|
||
postprandial2hBloodGlucose: '',
|
||
hba1c: '',
|
||
hyperlipidemia: 0,
|
||
totalCholesterol: '',
|
||
triglyceride: '',
|
||
ldlC: '',
|
||
hdlC: '',
|
||
medicalHistory: '',
|
||
medicationHistory: ''
|
||
},
|
||
trackingList: [],
|
||
showGenderPicker: false,
|
||
showAnemiaPicker: false,
|
||
showThalassemiaPicker: false,
|
||
showBirthPicker: false,
|
||
showChainPicker: false,
|
||
showMerchantPicker: false,
|
||
merchantLoading: false,
|
||
merchantChainList: [],
|
||
merchantSearchKeyword: '',
|
||
merchantPickerValue: [0],
|
||
showTrackingProductPicker: false,
|
||
trackingProductLoading: false,
|
||
trackingProductList: [],
|
||
trackingProductKeyword: '',
|
||
trackingProductLoadedChain: '',
|
||
activeTrackingIndex: -1,
|
||
showTrackingDatePicker: false,
|
||
activeTrackingDateIndex: -1,
|
||
showReferencePopup: false,
|
||
referencePopupTitle: '',
|
||
referencePopupContent: '',
|
||
minDate: dayjs().subtract(100, 'year').valueOf(),
|
||
}
|
||
},
|
||
computed: {
|
||
birthPickerValue() {
|
||
return this.form.birthday || Date.now()
|
||
},
|
||
trackingDatePickerValue() {
|
||
const item = this.trackingList[this.activeTrackingDateIndex]
|
||
return item && item.purchaseTime ? dayjs(item.purchaseTime).valueOf() : Date.now()
|
||
},
|
||
chainOptions() {
|
||
const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : []
|
||
return list.length ? list.map(item => ({id: item.id, text: item.text})) : [{id: '', text: '暂无连锁'}]
|
||
},
|
||
merchantOptions() {
|
||
// 门店明细挂在实例的 merchantStore 上,不进页面 data(见 setMerchantChains)
|
||
const chainList = Array.isArray(this.merchantChainList) ? this.merchantChainList : []
|
||
const hasChain = chainList.some(item => String(item.id) === String(this.form.chainId))
|
||
const merchants = hasChain ? this.getMerchantsByChainId(this.form.chainId) : []
|
||
return merchants.length ? merchants : [{id: '', text: '无门店'}]
|
||
},
|
||
merchantFilteredOptions() {
|
||
const list = Array.isArray(this.merchantOptions) ? this.merchantOptions : []
|
||
const keyword = (this.merchantSearchKeyword || '').trim()
|
||
if (!keyword) return list
|
||
const filtered = list.filter(item => (item.text || '').includes(keyword))
|
||
return filtered.length ? filtered : [{id: '', text: '无匹配门店'}]
|
||
},
|
||
chainPickerIndex() {
|
||
const chainList = Array.isArray(this.merchantChainList) ? this.merchantChainList : []
|
||
if (!chainList.length) return [0]
|
||
let chainIndex = chainList.findIndex(item => String(item.id) === String(this.form.chainId))
|
||
if (chainIndex < 0) chainIndex = 0
|
||
return [chainIndex]
|
||
},
|
||
merchantPickerIndex() {
|
||
const merchants = Array.isArray(this.merchantFilteredOptions) ? this.merchantFilteredOptions : []
|
||
let merchantIndex = merchants.findIndex(item => String(item.id) === String(this.form.merchantId))
|
||
if (merchantIndex < 0) merchantIndex = 0
|
||
return [merchantIndex]
|
||
},
|
||
merchantIndicatorStyle() {
|
||
return 'height: 64px;'
|
||
},
|
||
filteredTrackingProducts() {
|
||
const list = Array.isArray(this.trackingProductList) ? this.trackingProductList : []
|
||
const keyword = String(this.trackingProductKeyword || '').trim()
|
||
if (!keyword) return list
|
||
return list.filter(item => {
|
||
const text = [
|
||
item.productName,
|
||
item.goodsCode,
|
||
item.dailyUsageCount,
|
||
item.dosagePerTime,
|
||
item.boxUsageDays
|
||
].join(' ')
|
||
return text.includes(keyword)
|
||
})
|
||
}
|
||
},
|
||
watch: {
|
||
merchantSearchKeyword() {
|
||
this.merchantPickerValue = [0]
|
||
}
|
||
},
|
||
created() {
|
||
// 门店明细挂实例上而非 data:data 里的一切都会进页面 data 树并被 setData 序列化
|
||
this.merchantStore = []
|
||
},
|
||
onShow() {
|
||
this.fetchMerchantList()
|
||
},
|
||
methods: {
|
||
async fetchMerchantList() {
|
||
this.merchantLoading = true
|
||
try {
|
||
const {data} = await merchantGroupByChainReq({})
|
||
const list = this.unwrapList(data)
|
||
const chains = list
|
||
.map((item, index) => this.normalizeMerchantChain(item, index))
|
||
.filter(item => Array.isArray(item.merchants) && item.merchants.length > 0)
|
||
this.setMerchantChains(chains)
|
||
} catch (error) {
|
||
this.setMerchantChains([])
|
||
uni.showToast({
|
||
title: '门店列表获取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.merchantLoading = false
|
||
}
|
||
},
|
||
// 门店明细(全量可达 8000+ 条)只放在实例上,不进页面 data。
|
||
// 一旦放进 data,页面 data 树会膨胀到 1MB+,此后每次 setData 都要重新
|
||
// diff/序列化整棵树,连只含一个字段的 setData 也会从 ~490ms 涨到 ~1000ms。
|
||
setMerchantChains(chains) {
|
||
const list = Array.isArray(chains) ? chains : []
|
||
this.merchantStore = list.map(chain => ({
|
||
id: chain.id,
|
||
text: chain.text,
|
||
merchants: Array.isArray(chain.merchants) ? chain.merchants : []
|
||
}))
|
||
this.merchantChainList = this.merchantStore.map(chain => ({
|
||
id: chain.id,
|
||
text: chain.text,
|
||
merchantCount: chain.merchants.length
|
||
}))
|
||
},
|
||
getMerchantsByChainId(chainId) {
|
||
const store = Array.isArray(this.merchantStore) ? this.merchantStore : []
|
||
const chain = store.find(item => String(item.id) === String(chainId))
|
||
return chain && Array.isArray(chain.merchants) ? chain.merchants : []
|
||
},
|
||
openChainPicker() {
|
||
this.showChainPicker = true
|
||
},
|
||
closeChainPicker() {
|
||
this.showChainPicker = false
|
||
},
|
||
onChainConfirm(e) {
|
||
const selected = e && e.value && e.value.length ? e.value[0] : null
|
||
const nextChainId = selected ? (selected.id ?? '') : ''
|
||
const nextChainName = selected ? (selected.text || '') : ''
|
||
const changed = String(this.form.chainId) !== String(nextChainId) || String(this.form.chainName) !== String(nextChainName)
|
||
this.form.chainId = nextChainId
|
||
this.form.chainName = nextChainName
|
||
if (changed) {
|
||
this.form.merchantId = ''
|
||
this.form.merchantName = ''
|
||
this.resetTrackingList()
|
||
this.fetchTrackingProducts()
|
||
}
|
||
this.showChainPicker = false
|
||
},
|
||
openMerchantPicker() {
|
||
if (!this.form.chainId) {
|
||
uni.showToast({
|
||
title: '请先选择连锁',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.merchantSearchKeyword = ''
|
||
this.merchantPickerValue = [...this.merchantPickerIndex]
|
||
this.showMerchantPicker = true
|
||
},
|
||
closeMerchantPicker() {
|
||
this.showMerchantPicker = false
|
||
},
|
||
onMerchantPickerChange(e) {
|
||
const value = Array.isArray(e.detail && e.detail.value) ? e.detail.value : [0]
|
||
this.merchantPickerValue = value
|
||
},
|
||
confirmMerchantPicker() {
|
||
const selected = this.merchantFilteredOptions[Number(this.merchantPickerValue[0] || 0)] || null
|
||
if (!selected || selected.id === '' || selected.id === null || selected.id === undefined) {
|
||
this.showMerchantPicker = false
|
||
return
|
||
}
|
||
this.form.merchantName = selected.text || selected.merchantName || ''
|
||
this.form.merchantId = selected.id ?? selected.merchantId ?? ''
|
||
this.showMerchantPicker = false
|
||
},
|
||
openGenderPicker() {
|
||
this.showGenderPicker = true
|
||
},
|
||
closeGenderPicker() {
|
||
this.showGenderPicker = false
|
||
},
|
||
onGenderConfirm(e) {
|
||
this.form.gender = e.value && e.value.length ? e.value[0] : ''
|
||
this.closeGenderPicker()
|
||
},
|
||
openAnemiaPicker() {
|
||
this.showAnemiaPicker = true
|
||
},
|
||
closeAnemiaPicker() {
|
||
this.showAnemiaPicker = false
|
||
},
|
||
onAnemiaConfirm(e) {
|
||
this.form.anemiaLevel = e.value && e.value.length ? e.value[0] : '否'
|
||
this.closeAnemiaPicker()
|
||
},
|
||
openThalassemiaPicker() {
|
||
this.showThalassemiaPicker = true
|
||
},
|
||
closeThalassemiaPicker() {
|
||
this.showThalassemiaPicker = false
|
||
},
|
||
onThalassemiaConfirm(e) {
|
||
this.form.thalassemiaType = e.value && e.value.length ? e.value[0] : '否'
|
||
this.closeThalassemiaPicker()
|
||
},
|
||
openBirthPicker() {
|
||
this.showBirthPicker = true
|
||
},
|
||
closeBirthPicker() {
|
||
this.showBirthPicker = false
|
||
},
|
||
onBirthConfirm(e) {
|
||
this.form.birthday = dayjs(e.value).format('YYYY-MM-DD')
|
||
this.closeBirthPicker()
|
||
},
|
||
openReferencePopup(title, content) {
|
||
this.referencePopupTitle = title
|
||
this.referencePopupContent = content
|
||
this.showReferencePopup = true
|
||
},
|
||
closeReferencePopup() {
|
||
this.showReferencePopup = false
|
||
},
|
||
showHemoglobinReference() {
|
||
this.openReferencePopup(
|
||
'血红蛋白参考值',
|
||
'成年男性\n120g/L-160g/L\n\n成年女性\n110g/L-150g/L\n\n新生儿\n170g/L-200g/L\n\n妊娠期贫血程度表\n轻度:90g/L-109g/L\n中度:70g/L-89g/L\n重度:<70g/L'
|
||
)
|
||
},
|
||
showHighBloodPressureReference() {
|
||
this.openReferencePopup(
|
||
'高血压参考值',
|
||
'收缩压(高压)\n正常参考值:<120 mmHg\n临界/需要注意:120~139 mmHg\n诊断标准:≥140 mmHg\n\n舒张压(低压)\n正常参考值:<80 mmHg\n临界/需要注意:80~89 mmHg\n诊断标准:≥90 mmHg'
|
||
)
|
||
},
|
||
showHighBloodSugarReference() {
|
||
this.openReferencePopup(
|
||
'高血糖参考值',
|
||
'空腹血糖\n正常参考值:3.9~6.1 mmol/L\n临界/需要注意:6.1~7.0 mmol/L(空腹血糖受损)\n诊断标准:≥7.0 mmol/L\n\n餐后2小时血糖\n正常参考值:<7.8 mmol/L\n临界/需要注意:7.8~11.1 mmol/L(糖耐量异常)\n诊断标准:≥11.1 mmol/L\n\n糖化血红蛋白(HbA1c)\n正常参考值:<5.7%\n临界/需要注意:5.7%~6.4%(糖尿病前期)\n诊断标准:≥6.5%'
|
||
)
|
||
},
|
||
showHyperlipidemiaReference() {
|
||
this.openReferencePopup(
|
||
'高血脂参考值',
|
||
'总胆固醇(TC)\n正常:<5.2 mmol/L\n临界/需要注意:5.2~6.2 mmol/L\n诊断标准:≥6.2 mmol/L\n\n甘油三酯(TG)\n正常:<1.7 mmol/L\n临界/需要注意:1.7~2.3 mmol/L\n诊断标准:≥2.3 mmol/L\n\n低密度脂蛋白(LDL-C)\n正常:<3.4 mmol/L\n临界/需要注意:3.4~4.1 mmol/L\n诊断标准:≥4.1 mmol/L\n\n高密度脂蛋白(HDL-C)\n正常:男>1.0 mmol/L,女>1.3 mmol/L\n临界/需要注意:-\n诊断标准:<1.0 mmol/L(数值越低风险越高)'
|
||
)
|
||
},
|
||
setConditions(e) {
|
||
const selected = e.detail.value || []
|
||
this.form.highBloodPressure = selected.includes('highBloodPressure') ? 1 : 0
|
||
this.form.highBloodSugar = selected.includes('highBloodSugar') ? 1 : 0
|
||
this.form.hyperlipidemia = selected.includes('hyperlipidemia') ? 1 : 0
|
||
if (this.form.highBloodPressure !== 1) {
|
||
this.form.highBloodPressureValue = ''
|
||
this.form.systolicPressure = ''
|
||
this.form.diastolicPressure = ''
|
||
}
|
||
if (this.form.highBloodSugar !== 1) {
|
||
this.form.fastingBloodGlucose = ''
|
||
this.form.postprandial2hBloodGlucose = ''
|
||
this.form.hba1c = ''
|
||
}
|
||
if (this.form.hyperlipidemia !== 1) {
|
||
this.form.totalCholesterol = ''
|
||
this.form.triglyceride = ''
|
||
this.form.ldlC = ''
|
||
this.form.hdlC = ''
|
||
}
|
||
},
|
||
createTrackingItem() {
|
||
return {
|
||
_key: `tracking_${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
||
productId: '',
|
||
goodsCode: '',
|
||
productName: '',
|
||
dailyUsageCount: '',
|
||
dosagePerTime: '',
|
||
boxUsageDays: '',
|
||
purchaseTime: '',
|
||
boxCount: '',
|
||
estimatedFinishTime: ''
|
||
}
|
||
},
|
||
addTrackingItem() {
|
||
this.trackingList.push(this.createTrackingItem())
|
||
},
|
||
removeTrackingItem(index) {
|
||
this.trackingList.splice(index, 1)
|
||
},
|
||
resetTrackingList() {
|
||
this.trackingList = []
|
||
this.trackingProductList = []
|
||
this.trackingProductKeyword = ''
|
||
this.trackingProductLoadedChain = ''
|
||
this.activeTrackingIndex = -1
|
||
this.activeTrackingDateIndex = -1
|
||
this.showTrackingProductPicker = false
|
||
this.showTrackingDatePicker = false
|
||
},
|
||
normalizeTrackingProduct(item) {
|
||
const product = item && item.product && typeof item.product === 'object' ? item.product : {}
|
||
const productId = item.productId || item.product_id || item.id || item.goodsId || item.goods_id || product.productId || product.id || ''
|
||
const goodsCode = item.goodsCode || item.goods_code || item.code || item.productCode || item.product_code || product.goodsCode || product.code || ''
|
||
const productName = item.productName || item.product_name || item.goodsName || item.goods_name || item.name || item.title || product.productName || product.goodsName || product.name || product.title || ''
|
||
const dailyUsageCount = item.dailyUsageCount || item.daily_usage_count || product.dailyUsageCount || product.daily_usage_count || ''
|
||
const dosagePerTime = item.dosagePerTime || item.dosage_per_time || product.dosagePerTime || product.dosage_per_time || ''
|
||
const boxUsageDays = item.boxUsageDays || item.box_usage_days || item.usageDays || item.usage_days || product.boxUsageDays || product.box_usage_days || product.usageDays || product.usage_days || ''
|
||
return {
|
||
productId,
|
||
goodsCode,
|
||
productName,
|
||
dailyUsageCount,
|
||
dosagePerTime,
|
||
boxUsageDays
|
||
}
|
||
},
|
||
uniqueTrackingProducts(list) {
|
||
const map = {}
|
||
;(Array.isArray(list) ? list : []).forEach(item => {
|
||
const key = item.productId || item.goodsCode || `${item.productName}_${item.dailyUsageCount}_${item.dosagePerTime}_${item.boxUsageDays}`
|
||
if (!key || map[key]) return
|
||
map[key] = item
|
||
})
|
||
return Object.keys(map).map(key => map[key])
|
||
},
|
||
async fetchTrackingProducts() {
|
||
const chain = String(this.form.chainName || '').trim()
|
||
if (!chain) {
|
||
this.trackingProductList = []
|
||
this.trackingProductLoadedChain = ''
|
||
return
|
||
}
|
||
this.trackingProductLoading = true
|
||
try {
|
||
const {data} = await chronicDiseaseProductListReq({chain})
|
||
if (chain !== String(this.form.chainName || '').trim()) return
|
||
const list = this.unwrapList(data)
|
||
.map(item => this.normalizeTrackingProduct(item))
|
||
.filter(item => !!item.productName)
|
||
this.trackingProductList = this.uniqueTrackingProducts(list)
|
||
this.trackingProductLoadedChain = chain
|
||
} catch (error) {
|
||
this.trackingProductList = []
|
||
this.trackingProductLoadedChain = ''
|
||
uni.showToast({
|
||
title: '药品列表获取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.trackingProductLoading = false
|
||
}
|
||
},
|
||
async openTrackingProductPicker(index) {
|
||
if (!this.form.chainName) {
|
||
uni.showToast({
|
||
title: '请先选择连锁',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.activeTrackingIndex = index
|
||
this.trackingProductKeyword = ''
|
||
this.showTrackingProductPicker = true
|
||
const chain = String(this.form.chainName || '').trim()
|
||
if (this.trackingProductLoadedChain !== chain || !this.trackingProductList.length) {
|
||
await this.fetchTrackingProducts()
|
||
}
|
||
},
|
||
closeTrackingProductPicker() {
|
||
this.showTrackingProductPicker = false
|
||
},
|
||
selectTrackingProduct(productIndex) {
|
||
const product = this.filteredTrackingProducts[productIndex]
|
||
const trackingIndex = this.activeTrackingIndex
|
||
if (!product || trackingIndex < 0 || !this.trackingList[trackingIndex]) {
|
||
this.closeTrackingProductPicker()
|
||
return
|
||
}
|
||
const next = Object.assign({}, this.trackingList[trackingIndex], {
|
||
productId: product.productId || '',
|
||
goodsCode: product.goodsCode || '',
|
||
productName: product.productName || '',
|
||
dailyUsageCount: product.dailyUsageCount || '',
|
||
dosagePerTime: product.dosagePerTime || '',
|
||
boxUsageDays: product.boxUsageDays || ''
|
||
})
|
||
next.estimatedFinishTime = this.calculateTrackingFinish(next)
|
||
this.$set(this.trackingList, trackingIndex, next)
|
||
this.closeTrackingProductPicker()
|
||
},
|
||
openTrackingDatePicker(index) {
|
||
this.activeTrackingDateIndex = index
|
||
this.showTrackingDatePicker = true
|
||
},
|
||
closeTrackingDatePicker() {
|
||
this.showTrackingDatePicker = false
|
||
},
|
||
onTrackingDateConfirm(e) {
|
||
const index = this.activeTrackingDateIndex
|
||
if (index > -1 && this.trackingList[index]) {
|
||
const next = Object.assign({}, this.trackingList[index], {
|
||
purchaseTime: dayjs(e.value).format('YYYY-MM-DD')
|
||
})
|
||
next.estimatedFinishTime = this.calculateTrackingFinish(next)
|
||
this.$set(this.trackingList, index, next)
|
||
}
|
||
this.closeTrackingDatePicker()
|
||
},
|
||
onTrackingBoxInput(index, e) {
|
||
if (!this.trackingList[index]) return
|
||
const value = e && e.detail ? e.detail.value : ''
|
||
const next = Object.assign({}, this.trackingList[index], {
|
||
boxCount: value
|
||
})
|
||
next.estimatedFinishTime = this.calculateTrackingFinish(next)
|
||
this.$set(this.trackingList, index, next)
|
||
},
|
||
parseNumber(value) {
|
||
if (typeof value === 'number') return value
|
||
const match = String(value || '').match(/\d+(\.\d+)?/)
|
||
return match ? Number(match[0]) : NaN
|
||
},
|
||
calculateTrackingFinish(item) {
|
||
const purchaseTime = item && item.purchaseTime ? item.purchaseTime : ''
|
||
const boxUsageDays = this.parseNumber(item && item.boxUsageDays)
|
||
const boxCount = this.parseNumber(item && item.boxCount)
|
||
if (!purchaseTime || !Number.isFinite(boxUsageDays) || boxUsageDays <= 0 || !Number.isFinite(boxCount) || boxCount <= 0) {
|
||
return ''
|
||
}
|
||
return dayjs(purchaseTime).add(boxUsageDays * boxCount, 'day').format('YYYY-MM-DD')
|
||
},
|
||
buildTrackingPayload() {
|
||
const result = []
|
||
for (let i = 0; i < this.trackingList.length; i += 1) {
|
||
const item = this.trackingList[i]
|
||
const productName = String(item.productName || '').trim()
|
||
const purchaseTime = String(item.purchaseTime || '').trim()
|
||
const boxCount = this.parseNumber(item.boxCount)
|
||
const boxUsageDays = this.parseNumber(item.boxUsageDays)
|
||
if (!productName || !purchaseTime || !Number.isFinite(boxCount) || boxCount <= 0) {
|
||
uni.showToast({
|
||
title: `请完善第${i + 1}条用药追踪`,
|
||
icon: 'none'
|
||
})
|
||
return null
|
||
}
|
||
if (!Number.isFinite(boxUsageDays) || boxUsageDays <= 0 || !item.estimatedFinishTime) {
|
||
uni.showToast({
|
||
title: `第${i + 1}条药品缺少服用天数`,
|
||
icon: 'none'
|
||
})
|
||
return null
|
||
}
|
||
result.push({
|
||
productId: item.productId || '',
|
||
productName,
|
||
goodsCode: item.goodsCode || '',
|
||
dailyUsageCount: item.dailyUsageCount || '',
|
||
dosagePerTime: item.dosagePerTime || '',
|
||
boxUsageDays,
|
||
purchaseTime,
|
||
boxCount,
|
||
estimatedFinishTime: item.estimatedFinishTime
|
||
})
|
||
}
|
||
return result
|
||
},
|
||
async submitForm() {
|
||
if (!this.form.chainId || !this.form.merchantId) {
|
||
uni.showToast({
|
||
title: '请选择连锁和门店',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (!this.form.name || !this.form.phone) {
|
||
uni.showToast({
|
||
title: '请先填写姓名和电话',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const systolicPressure = String(this.form.systolicPressure || '').trim()
|
||
const diastolicPressure = String(this.form.diastolicPressure || '').trim()
|
||
if (this.form.highBloodPressure === 1 && (!systolicPressure || !diastolicPressure)) {
|
||
uni.showToast({
|
||
title: '请填写收缩压和舒张压',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const highBloodPressureValue = this.form.highBloodPressure === 1
|
||
? `${systolicPressure}/${diastolicPressure}`
|
||
: ''
|
||
const fastingBloodGlucose = String(this.form.fastingBloodGlucose || '').trim()
|
||
const postprandial2hBloodGlucose = String(this.form.postprandial2hBloodGlucose || '').trim()
|
||
const hba1c = String(this.form.hba1c || '').trim()
|
||
if (this.form.highBloodSugar === 1 && (!fastingBloodGlucose || !postprandial2hBloodGlucose || !hba1c)) {
|
||
uni.showToast({
|
||
title: '请填写完整高血糖指标',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const totalCholesterol = String(this.form.totalCholesterol || '').trim()
|
||
const triglyceride = String(this.form.triglyceride || '').trim()
|
||
const ldlC = String(this.form.ldlC || '').trim()
|
||
const hdlC = String(this.form.hdlC || '').trim()
|
||
if (this.form.hyperlipidemia === 1 && (!totalCholesterol || !triglyceride || !ldlC || !hdlC)) {
|
||
uni.showToast({
|
||
title: '请填写完整高血脂指标',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const hemoglobinValue = String(this.form.hemoglobinValue || '').trim()
|
||
const trackingPayload = this.buildTrackingPayload()
|
||
if (trackingPayload === null) return
|
||
await chronicDiseaseCollectionAddReq({
|
||
...this.form,
|
||
tracking: JSON.stringify(trackingPayload),
|
||
hemoglobinValue,
|
||
anemiaLevel: this.form.anemiaLevel || '否',
|
||
thalassemiaType: this.form.thalassemiaType || '否',
|
||
systolicPressure,
|
||
diastolicPressure,
|
||
highBloodPressureValue,
|
||
fastingBloodGlucose,
|
||
postprandial2hBloodGlucose,
|
||
hba1c,
|
||
totalCholesterol,
|
||
triglyceride,
|
||
ldlC,
|
||
hdlC,
|
||
chainId: this.form.chainId,
|
||
chainName: this.form.chainName,
|
||
merchantId: this.form.merchantId,
|
||
merchantName: this.form.merchantName
|
||
})
|
||
uni.showToast({
|
||
title: '慢病信息已提交',
|
||
icon: 'success'
|
||
})
|
||
setTimeout(() => {
|
||
uni.navigateBack()
|
||
}, 1500)
|
||
},
|
||
normalizeMerchantChain(item, index) {
|
||
const chainName = this.pickFirstText(item, ['chainName', 'chain', 'chainAbb', 'name', 'title']) || `连锁${index + 1}`
|
||
const rawChainId = item.chainId ?? item.chain_id ?? item.id ?? item.chainCode ?? item.chain_code ?? chainName
|
||
const chainId = rawChainId === undefined || rawChainId === null || String(rawChainId).trim() === ''
|
||
? `chain_${index}`
|
||
: rawChainId
|
||
const childCandidates = ['children', 'merchantList', 'merchants', 'shopList', 'list', 'records']
|
||
let rawChildren = []
|
||
for (let i = 0; i < childCandidates.length; i++) {
|
||
const list = item[childCandidates[i]]
|
||
if (Array.isArray(list) && list.length) {
|
||
rawChildren = list
|
||
break
|
||
}
|
||
}
|
||
let merchants = rawChildren
|
||
.map(child => this.normalizeMerchant(child))
|
||
.filter(child => child.id !== undefined && child.id !== null && String(child.id).trim() !== '')
|
||
if (!merchants.length) {
|
||
const selfMerchant = this.normalizeMerchant(item)
|
||
if (selfMerchant.id !== undefined && selfMerchant.id !== null && String(selfMerchant.id).trim() !== '') {
|
||
merchants = [selfMerchant]
|
||
}
|
||
}
|
||
return {
|
||
id: chainId,
|
||
text: chainName,
|
||
merchants: merchants.map(merchant => Object.assign({}, merchant, {chainId, chainName}))
|
||
}
|
||
},
|
||
normalizeMerchant(item) {
|
||
const id = item.id ?? item.merchantId ?? item.merchant_id
|
||
const text = item.name || item.merchantName || item.merchant_name || item.title || '未命名门店'
|
||
return {
|
||
id,
|
||
text
|
||
}
|
||
},
|
||
applyMerchant(chain, merchant) {
|
||
if (!merchant) return
|
||
this.form.chainName = (chain && chain.text) || merchant.chainName || ''
|
||
this.form.chainId = (chain && chain.id) ?? merchant.chainId ?? ''
|
||
this.form.merchantName = merchant.text || merchant.merchantName || ''
|
||
this.form.merchantId = merchant.id ?? merchant.merchantId ?? ''
|
||
},
|
||
pickFirstText(source, keys = []) {
|
||
if (!source || typeof source !== 'object') return ''
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const value = source[keys[i]]
|
||
if (value !== undefined && value !== null && String(value).trim()) {
|
||
return String(value).trim()
|
||
}
|
||
}
|
||
return ''
|
||
},
|
||
unwrapList(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result', 'chainList']
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const value = data[keys[i]]
|
||
if (Array.isArray(value)) return value
|
||
if (value && typeof value === 'object') {
|
||
const nested = this.unwrapList(value)
|
||
if (nested.length) return nested
|
||
}
|
||
}
|
||
return [data]
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.chronic-page {
|
||
min-height: 100vh;
|
||
//padding: 0 20rpx 40rpx;
|
||
background: #f5f5f5;
|
||
}
|
||
|
||
.page-hero {
|
||
//padding: 30rpx 6rpx 18rpx;
|
||
background: url('https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/20260126/462bfa04cf0eff8698884c38b9d17519.png') no-repeat
|
||
top center / 100% auto;
|
||
}
|
||
|
||
.page-navbar {
|
||
padding: 0 6rpx;
|
||
}
|
||
|
||
.intro-card {
|
||
margin: 18rpx 20rpx 0;
|
||
padding: 20rpx 18rpx;
|
||
border-radius: 20rpx;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.card-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.title-bar {
|
||
width: 6rpx;
|
||
height: 30rpx;
|
||
border-radius: 999rpx;
|
||
background: #2fc6a7;
|
||
}
|
||
|
||
.intro-text {
|
||
margin-top: 12rpx;
|
||
font-size: 26rpx;
|
||
color: #6b7280;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.intro-meta {
|
||
margin-top: 8rpx;
|
||
font-size: 24rpx;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.block {
|
||
margin: 18rpx 20rpx 0;
|
||
}
|
||
|
||
.form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 10rpx 0;
|
||
border-bottom: 1rpx solid #edf1f7;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.form-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.form-row > text {
|
||
flex: 1;
|
||
max-width: 62%;
|
||
color: var(--text-main);
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.form-row input,
|
||
.form-row .picker-value {
|
||
flex: 1;
|
||
text-align: right;
|
||
}
|
||
|
||
.picker-value {
|
||
color: var(--text-main);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.readonly-value {
|
||
flex: 1;
|
||
text-align: right;
|
||
color: #2563eb;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.section-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.tracking-add-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 104rpx;
|
||
height: 52rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 999rpx;
|
||
background: #e8f7f3;
|
||
color: #0f9f83;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tracking-tip {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.6;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.tracking-empty {
|
||
padding: 30rpx 0;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.tracking-item {
|
||
margin-top: 18rpx;
|
||
padding: 16rpx;
|
||
border-radius: 16rpx;
|
||
background: #f8fbff;
|
||
border: 1rpx solid #e3eaf4;
|
||
}
|
||
|
||
.tracking-item-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8rpx;
|
||
color: #1f2937;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.tracking-remove {
|
||
color: #ef4444;
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tracking-product-value {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.tracking-product-meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6rpx;
|
||
margin-bottom: 4rpx;
|
||
padding: 10rpx 14rpx;
|
||
border-radius: 12rpx;
|
||
background: #ffffff;
|
||
color: #6b7280;
|
||
font-size: 23rpx;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.conditions {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.pressure-form {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.ref-row {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.ref-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 8rpx 22rpx;
|
||
border-radius: 999rpx;
|
||
border: 1rpx solid #2563eb;
|
||
color: #2563eb;
|
||
font-size: 24rpx;
|
||
line-height: 1;
|
||
}
|
||
|
||
.condition {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
padding: 10rpx 14rpx;
|
||
border-radius: 999rpx;
|
||
background: #f4f7fb;
|
||
border: 1rpx solid #e3eaf4;
|
||
}
|
||
|
||
.condition-text {
|
||
color: var(--text-main);
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.textarea {
|
||
width: 100%;
|
||
min-height: 160rpx;
|
||
padding: 16rpx;
|
||
background: #f7fbff;
|
||
border-radius: 12rpx;
|
||
border: 1rpx solid #e3eaf4;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.submit-btn {
|
||
width: 90%;
|
||
text-align: center;
|
||
margin: 20rpx auto;
|
||
}
|
||
|
||
.reference-popup {
|
||
width: 620rpx;
|
||
max-height: 900rpx;
|
||
padding: 28rpx 24rpx 22rpx;
|
||
box-sizing: border-box;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.reference-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
text-align: center;
|
||
}
|
||
|
||
.reference-content {
|
||
max-height: 680rpx;
|
||
margin-top: 22rpx;
|
||
padding: 0 8rpx;
|
||
}
|
||
|
||
.reference-text {
|
||
white-space: pre-line;
|
||
font-size: 26rpx;
|
||
line-height: 1.7;
|
||
color: #374151;
|
||
}
|
||
|
||
.reference-confirm {
|
||
margin-top: 26rpx;
|
||
height: 76rpx;
|
||
border-radius: 999rpx;
|
||
background: #2563eb;
|
||
color: #ffffff;
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.merchant-picker {
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
background: #ffffff;
|
||
}
|
||
|
||
.tracking-product-picker {
|
||
padding-bottom: env(safe-area-inset-bottom);
|
||
background: #ffffff;
|
||
}
|
||
|
||
.merchant-picker-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 24rpx 28rpx 12rpx;
|
||
}
|
||
|
||
.picker-action {
|
||
font-size: 26rpx;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.picker-action.primary {
|
||
color: #2563eb;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.picker-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.merchant-picker-search {
|
||
padding: 0 24rpx 16rpx;
|
||
}
|
||
|
||
.merchant-search-input {
|
||
height: 72rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 999rpx;
|
||
background: #f3f6fb;
|
||
font-size: 26rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.merchant-search-placeholder {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.merchant-picker-loading {
|
||
height: 420rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.merchant-picker-view {
|
||
height: 420rpx;
|
||
}
|
||
|
||
.merchant-picker-item {
|
||
height: 84rpx;
|
||
line-height: 84rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.tracking-product-scroll {
|
||
height: 620rpx;
|
||
padding: 0 24rpx 24rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tracking-product-option {
|
||
padding: 20rpx 18rpx;
|
||
border-radius: 16rpx;
|
||
background: #f8fbff;
|
||
border: 1rpx solid #e3eaf4;
|
||
}
|
||
|
||
.tracking-product-option + .tracking-product-option {
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.tracking-product-name {
|
||
color: #1f2937;
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.tracking-product-option-meta {
|
||
margin-top: 8rpx;
|
||
color: #6b7280;
|
||
font-size: 24rpx;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
</style>
|