ykt-wxapp/pages/case/components/archive-detail/health-profile-tab.vue
2026-02-11 17:11:46 +08:00

869 lines
27 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>
<!-- Mobile 来源: ykt-management-mobile/src/pages/customer/customer-detail/health-profile/health-profile.vue -->
<view class="wrap">
<view class="filters">
<picker mode="selector" :range="typeRange" range-key="name" @change="pickType">
<view class="filter-pill">
<view class="pill-text">{{ currentType.value === 'ALL' ? '全部病历' : currentType.name }}</view>
<uni-icons type="arrowdown" size="12" color="#666" />
</view>
</picker>
<uni-datetime-picker type="daterange" v-model="dateRange" @change="pickTimeRange">
<view class="filter-pill">
<view class="pill-text">{{ dateRangeLabel }}</view>
<view class="pill-icons">
<view v-if="isDateRangeActive" class="pill-clear" @tap.stop="clearTimeRange" @click.stop="clearTimeRange">
<uni-icons type="closeempty" size="16" color="#666" />
</view>
<uni-icons type="arrowdown" size="12" color="#666" />
</view>
</view>
</uni-datetime-picker>
</view>
<view v-if="showShareTip" class="share-tip">
<text class="share-tip-text">患者已授权其他团队病历信息互通</text>
</view>
<view class="list">
<view v-for="r in records" :key="r._id" class="card record" @click="edit(r)">
<view class="record-head">
<view class="record-date">{{ r.dateStr || '--' }}</view>
<view class="record-tag" :class="tagClass[r.templateType] || 'bg-blue'">{{ r.tempName || '病历' }}</view>
<view v-if="r.corpName === '其他' || r.corp === '其他'" class="record-tag bg-rose">外院</view>
</view>
<view class="record-body">
<view v-for="(l, idx) in getDisplayLines(r)" :key="`${r._id}_${idx}`" class="line">
<text class="line-label">{{ l.label }}</text>
<text class="line-value">{{ l.value }}</text>
</view>
<view v-if="getFiles(r).length" class="thumbs">
<view v-for="(f, idx) in getFiles(r).slice(0, 3)" :key="idx" class="thumb" @click.stop="previewFiles(r, idx)">
<image class="thumb-img" :src="f.url" mode="aspectFill" />
</view>
<view v-if="getFiles(r).length > 3" class="thumb-more">+{{ getFiles(r).length - 3 }}</view>
</view>
</view>
<view class="record-foot">
<view class="foot-left">{{ getCreateFooter(r) }}</view>
</view>
</view>
<view v-if="records.length === 0" class="empty">暂无数据</view>
</view>
<picker
class="fab-picker"
mode="selector"
:range="selectableTemplates"
range-key="name"
:disabled="fabPickerDisabled"
:style="{ bottom: `${floatingBottom}px` }"
@change="pickAddType"
>
<view class="fab" :class="{ 'fab--disabled': selectableTemplates.length === 0 }" @tap="onFabTap">
<uni-icons type="plusempty" size="24" color="#fff" />
</view>
</picker>
</view>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { storeToRefs } from 'pinia';
import dayjs from 'dayjs';
import api from '@/utils/api';
import useAccountStore from '@/store/account';
import { loading, hideLoading, toast } from '@/utils/widget';
import { normalizeTemplate } from '../../utils/template';
import { normalizeVisitRecordFormData } from '../../utils/visit-record';
import { normalizeFileUrl } from '@/utils/file';
const props = defineProps({
data: { type: Object, default: () => ({}) },
archiveId: { type: String, default: '' },
floatingBottom: { type: Number, default: 16 },
});
const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
const { getDoctorInfo } = accountStore;
const FALLBACK_TEMPLATE_TYPES = ['outpatient', 'inhospital', 'preConsultationRecord', 'physicalExaminationTemplate'];
const templates = ref([]);
const selectableTemplates = computed(() => templates.value.filter((i) => i && i.templateType && typeof i.name === 'string' && i.name.trim()));
const useActionSheet = computed(() => selectableTemplates.value.length > 0 && selectableTemplates.value.length <= 6);
const fabPickerDisabled = computed(() => selectableTemplates.value.length === 0 || useActionSheet.value);
const templateMap = computed(() => templates.value.reduce((m, t) => {
if (t?.templateType) m[String(t.templateType)] = t;
return m;
}, {}));
const availableTypes = computed(() => (templates.value.length ? templates.value.map((i) => i.templateType) : FALLBACK_TEMPLATE_TYPES));
const typeRange = computed(() => [{ name: '全部', value: 'ALL' }, ...templates.value.map((t) => ({ name: t.name, value: t.templateType }))]);
const currentType = ref({ name: '全部', value: 'ALL' });
const records = ref([]);
const dateRange = ref([]);
const isDateRangeActive = computed(
() => Array.isArray(dateRange.value) && dateRange.value.length === 2 && dateRange.value[0] && dateRange.value[1]
);
const dateRangeLabel = computed(() => {
if (Array.isArray(dateRange.value) && dateRange.value.length === 2 && dateRange.value[0] && dateRange.value[1]) {
return `${dateRange.value[0]}${dateRange.value[1]}`;
}
return '全部时间';
});
function getCurrentTeamId() {
const team = uni.getStorageSync('ykt_case_current_team') || {};
return team?.teamId ? String(team.teamId) : '';
}
const teamId = ref(getCurrentTeamId());
const showShareTip = computed(() => {
if (props.data && typeof props.data.shareAllTeams === 'boolean') return props.data.shareAllTeams;
const list = props.data?.authorizedTeams;
return Array.isArray(list) && list.length > 1;
});
const shareAllTeamsForQuery = computed(() => {
if (props.data && typeof props.data.shareAllTeams === 'boolean') return props.data.shareAllTeams;
const list = props.data?.authorizedTeams;
if (Array.isArray(list)) return list.length > 1;
// mock 场景:没有授权信息时默认互通,避免误隐藏数据
return true;
});
function getCorpId() {
const team = uni.getStorageSync('ykt_case_current_team') || {};
const d = doctorInfo.value || {};
const a = account.value || {};
return String(team.corpId || d.corpId || a.corpId || '') || '';
}
async function ensureDoctor() {
if (doctorInfo.value) return;
if (!account.value?.openid) return;
try {
await getDoctorInfo();
} catch {
// ignore
}
}
let corpMemberBatchInflight = null;
async function batchLoadCorpMembers(userIds) {
const ids = Array.isArray(userIds) ? userIds.map((v) => String(v || '').trim()).filter(Boolean) : [];
if (!ids.length) return;
const uniq = Array.from(new Set(ids));
const unknown = uniq.filter((id) => {
const existing = userNameMap.value?.[id];
return !existing || existing === id;
});
if (!unknown.length) return;
if (corpMemberBatchInflight) return corpMemberBatchInflight;
await ensureDoctor();
const corpId = getCorpId();
if (!corpId) return;
corpMemberBatchInflight = (async () => {
try {
const res = await api(
'getCorpMember',
{
page: 1,
pageSize: Math.min(Math.max(unknown.length, 10), 500),
params: {
corpId,
memberList: unknown,
},
},
false
);
if (!res?.success) return;
const rows = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : [];
if (!rows.length) return;
const patch = rows.reduce((acc, m) => {
const id = String(m?.userid || m?.userId || m?.corpUserId || '').trim();
if (!id) return acc;
const existing = userNameMap.value?.[id];
if (existing && existing !== id) return acc;
const display = String(m?.anotherName || m?.name || '').trim();
if (!display || display === id) return acc;
acc[id] = display;
return acc;
}, {});
if (Object.keys(patch).length) userNameMap.value = { ...(userNameMap.value || {}), ...patch };
} catch {
// ignore
}
})().finally(() => {
corpMemberBatchInflight = null;
});
return corpMemberBatchInflight;
}
const loadedCorpId = ref('');
let loadVisitTemplatesPromise = null;
let loadVisitTemplatesCorpId = '';
async function loadVisitTemplates() {
const corpId = getCorpId();
if (!corpId) return;
if (loadedCorpId.value === corpId && templates.value.length) return;
if (loadVisitTemplatesPromise && loadVisitTemplatesCorpId === corpId) return loadVisitTemplatesPromise;
loadVisitTemplatesCorpId = corpId;
loadVisitTemplatesPromise = (async () => {
const groupRes = await api('getTemplateGroup', { corpId, parentType: 'medicalRecord' });
const group = groupRes?.data && Array.isArray(groupRes.data?.data) ? groupRes.data.data : Array.isArray(groupRes?.data) ? groupRes.data : [];
const list = Array.isArray(group) ? group : [];
const groupNameMap = list.reduce((m, i) => {
const t = i?.templateType ? String(i.templateType) : '';
const name = i?.name ? String(i.name) : '';
if (t && name) m[t] = name;
return m;
}, {});
const enabled = list.filter((i) => i && i.templateType !== 'healthTemplate' && i.templateStatus !== 'disable');
const typeList = enabled.map((i) => String(i.templateType || '')).filter(Boolean);
if (!typeList.length) return;
const detailRes = await api('getTemplateListByTemptype', { corpId, templateTypeList: typeList });
const detail = detailRes?.data && Array.isArray(detailRes.data?.data) ? detailRes.data.data : Array.isArray(detailRes?.data) ? detailRes.data : [];
const temps = Array.isArray(detail) ? detail : [];
const byType = temps.reduce((m, t) => {
const k = t?.templateType ? String(t.templateType) : '';
if (k) m[k] = t;
return m;
}, {});
const ordered = typeList.map((t) => byType[String(t)]).filter(Boolean);
const next = ordered
.map((t) => {
const temp = normalizeTemplate(t);
const rawType = String(temp?.templateType || '');
const name = String(groupNameMap[rawType] || temp?.name || temp?.templateName || temp?.templateTypeName || '') || rawType;
return {
templateType: rawType,
name,
service: temp?.service || {},
templateList: Array.isArray(temp?.templateList) ? temp.templateList : [],
};
})
.filter((i) => i && i.templateType);
if (next.length) {
templates.value = next;
loadedCorpId.value = corpId;
}
})()
.catch((e) => {
console.error('loadVisitTemplates error:', e);
})
.finally(() => {
if (loadVisitTemplatesCorpId === corpId) {
loadVisitTemplatesPromise = null;
loadVisitTemplatesCorpId = '';
}
});
return loadVisitTemplatesPromise;
}
const userNameMap = ref({});
const loadedTeamId = ref('');
function resolveUserName(userId) {
const id = String(userId || '');
if (!id) return '';
const map = userNameMap.value || {};
return String(map[id] || '') || id;
}
async function loadTeamMembers() {
const team = uni.getStorageSync('ykt_case_current_team') || {};
const teamId = team?.teamId ? String(team.teamId) : '';
const corpId = getCorpId();
if (!teamId || !corpId) return;
if (loadedTeamId.value === teamId && Object.keys(userNameMap.value || {}).length > 0) return;
loadedTeamId.value = teamId;
const res = await api('getTeamData', { corpId, teamId });
if (!res?.success) return;
const t = res?.data && typeof res.data === 'object' ? res.data : {};
const members = Array.isArray(t.memberList) ? t.memberList : [];
userNameMap.value = members.reduce((acc, m) => {
const uid = String(m?.userid || '');
if (!uid) return acc;
acc[uid] = String(m?.anotherName || m?.name || m?.userid || '') || uid;
return acc;
}, {});
}
function getSortTimeTitle(templateType) {
const rawType = String(templateType || '');
const ui = normalizeMedicalType(rawType);
// 预问诊记录:列表时间固定使用就诊日期字段,避免被模板配置的 timeTitle 覆盖
if (ui === 'preConsultationRecord') return 'consultationDate';
const t = templateMap.value[rawType] || {};
if (t?.service?.timeTitle) return String(t.service.timeTitle);
if (ui === 'outpatient') return 'visitTime';
if (ui === 'inhospital') return 'inhosDate';
if (ui === 'physicalExaminationTemplate') return 'inspectDate';
return '';
}
function normalizeText(v) {
if (Array.isArray(v)) return v.filter((i) => i !== null && i !== undefined && String(i).trim()).join('');
if (v === 0) return '0';
return v ? String(v) : '';
}
function formatPositiveFind(v, { withOpinion = false } = {}) {
if (Array.isArray(v)) {
const list = v
.map((i) => (i && typeof i === 'object' ? { category: i.category, opinion: i.opinion } : null))
.filter((i) => i && (i.category || i.opinion));
if (!list.length) return '';
if (!withOpinion) return list.map((i) => String(i.category || '').trim()).filter(Boolean).join('');
return list
.map((i) => {
const c = String(i.category || '').trim();
const o = String(i.opinion || '').trim();
if (c && o) return `${c}${o}`;
return c || o;
})
.filter(Boolean)
.join('');
}
return normalizeText(v);
}
function getTemplateName(type) {
const t = templateMap.value[String(type || '')];
return t?.name ? String(t.name) : '';
}
function toDateStr(sortTime) {
return formatAnyDate(sortTime, 'YYYY-MM-DD');
}
function normalizeMedicalType(raw) {
const s = String(raw || '').trim();
if (!s) return '';
const lower = s.toLowerCase();
if (lower.includes('preconsult') || (lower.includes('pre') && lower.includes('consult'))) return 'preConsultationRecord';
if (lower === 'outpatient' || lower === 'out_patient' || lower === 'out-patient') return 'outpatient';
if (lower === 'inhospital' || lower === 'in_hospital' || lower === 'in-hospital' || lower === 'inpatient') return 'inhospital';
if (lower === 'preconsultation' || lower === 'pre_consultation' || lower === 'pre-consultation') return 'preConsultationRecord';
if (lower === 'physicalexaminationtemplate' || lower === 'physicalexamination' || lower === 'physical_examination') return 'physicalExaminationTemplate';
if (s === 'outPatient') return 'outpatient';
if (s === 'inHospital') return 'inhospital';
if (s === 'preConsultation') return 'preConsultationRecord';
if (s === 'preConsultationRecord') return 'preConsultationRecord';
if (s === 'physicalExaminationTemplate') return 'physicalExaminationTemplate';
return s;
}
function parseAnyTimeMs(v) {
if (v === null || v === undefined) return 0;
if (typeof v === 'number') {
// 10位秒级时间戳
if (v > 1e9 && v < 1e12) return v * 1000;
return v;
}
const s = String(v).trim();
if (!s) return 0;
if (/^\d{10,13}$/.test(s)) return Number(s.length === 10 ? `${s}000` : s);
const d = dayjs(s);
return d.isValid() ? d.valueOf() : 0;
}
function formatAnyDate(v, fmt = 'YYYY-MM-DD') {
const ms = parseAnyTimeMs(v);
if (!ms) return '';
const d = dayjs(ms);
return d.isValid() ? d.format(fmt) : '';
}
function toDateTimeStr(ts) {
return formatAnyDate(ts, 'YYYY-MM-DD HH:mm');
}
async function refreshList() {
if (!props.archiveId) return;
const corpId = getCorpId();
if (!corpId) return;
await loadVisitTemplates();
loadTeamMembers();
loading('加载中...');
try {
const params = { memberId: props.archiveId, corpId };
// 添加类型筛选
params.medicalType =
currentType.value.value === 'ALL' ? availableTypes.value : currentType.value.value;
// 添加时间筛选
if (Array.isArray(dateRange.value) && dateRange.value.length === 2 && dateRange.value[0] && dateRange.value[1]) {
params.startTime = dayjs(dateRange.value[0]).startOf('day').valueOf();
params.endTime = dayjs(dateRange.value[1]).endOf('day').valueOf();
}
const res = await api('getCustomerMedicalRecord', params);
hideLoading();
// 接口返回结构兼容:
// - { success, message, list: [] }
// - { success, message, data: { list: [] } }
const list = Array.isArray(res?.list)
? res.list
: Array.isArray(res?.data?.list)
? res.data.list
: Array.isArray(res?.data?.data)
? res.data.data
: Array.isArray(res?.data?.data?.list)
? res.data.data.list
: Array.isArray(res?.data)
? res.data
: [];
if (list.length) {
const mapped = list.map((r) => {
const rawType = String(r?.medicalType || r?.templateType || '') || '';
const uiType = normalizeMedicalType(rawType);
const normalized = normalizeVisitRecordFormData(uiType, r);
const timeTitle = getSortTimeTitle(rawType);
const rawTime = timeTitle ? (normalized?.[timeTitle] ?? r?.[timeTitle]) : '';
const dateStr = rawTime ? formatAnyDate(rawTime, 'YYYY-MM-DD') : '';
return {
...r,
...normalized,
medicalType: rawType,
templateType: uiType,
rawTemplateType: rawType,
dateStr: dateStr || toDateStr(r?.sortTime),
createDateStr: r?.createTime ? formatAnyDate(r.createTime, 'YYYY-MM-DD') : '',
createTimeStr: toDateTimeStr(r?.createTime),
tempName: r?.tempName || getTemplateName(rawType) || '病历',
};
});
// 未互通时,仅展示本团队数据
records.value = !shareAllTeamsForQuery.value && teamId.value
? mapped.filter((i) => String(i?.teamId || '') === String(teamId.value))
: mapped;
const creatorIds = mapped
.map(
(i) =>
i.creator ||
i.creatorUserId ||
i.createUserId ||
i.executor ||
i.executorUserId
)
.filter(Boolean);
void batchLoadCorpMembers(creatorIds);
} else {
records.value = [];
}
} catch (error) {
hideLoading();
console.error('获取病历记录失败:', error);
records.value = [];
}
}
const tagClass = {
outpatient: 'bg-amber',
inhospital: 'bg-teal',
preConsultationRecord: 'bg-indigo',
physicalExaminationTemplate: 'bg-green',
};
function resolveRecordType(r) {
if (!r) return '';
const direct = normalizeMedicalType(r.uiType || r.templateType || r.rawTemplateType || r.medicalType || '');
if (direct) return direct;
// fallback by known fields
if (r.inspectDate || r.positiveFind || r.inspectSummary) return 'physicalExaminationTemplate';
if (r.inhosDate || r.surgeryName || r.surgeryDate || r.operationDate) return 'inhospital';
if (r.visitTime || r.disposePlan || r.treatmentPlan) return 'outpatient';
if (r.consultationDate || r.consultDate || r.presentIllness || r.presentIllnessHistory || r.pastHistory) return 'preConsultationRecord';
return '';
}
function getDiagnosis(r) {
if (!r) return '--';
const t = resolveRecordType(r);
if (t === 'preConsultationRecord') return normalizeText(r.chiefComplaint) || normalizeText(r.summary) || '--';
if (t === 'physicalExaminationTemplate') return formatPositiveFind(r.positiveFind) || normalizeText(r.summary) || '--';
if (t === 'outpatient' || t === 'inhospital') return normalizeText(r.diagnosisName || r.diagnosis) || normalizeText(r.summary) || '--';
return normalizeText(r.diagnosisName || r.diagnosis || r.summary) || '--';
}
function firstLine(v) {
const s = normalizeText(v);
return s || '--';
}
function getDisplayLines(r) {
const t = resolveRecordType(r);
if (t === 'outpatient') {
return [{ label: '门诊诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
}
if (t === 'inhospital') {
const lines = [{ label: '入院诊断:', value: firstLine(r.diagnosisName || r.diagnosis) }];
const surgery = normalizeText(r.surgeryName);
if (surgery) lines.push({ label: '手术名称:', value: surgery });
return lines;
}
if (t === 'physicalExaminationTemplate') {
return [{ label: '体检小结:', value: firstLine(r.summary || r.inspectSummary) }];
}
if (t === 'preConsultationRecord') {
const lines = [
{ label: '主诉:', value: firstLine(r.chiefComplaint) },
{ label: '现病史:', value: firstLine(r.presentIllness || r.presentIllnessHistory) },
];
const past = normalizeText(r.pastHistory);
if (past) lines.push({ label: '既往史:', value: past });
return lines;
}
return [{ label: '摘要:', value: firstLine(r.summary) }];
}
function getCreateFooter(r) {
const time = r?.createTimeStr || r?.createDateStr || '';
const byCustomer = r?.ignore === 'checkIn';
if (byCustomer) return time ? `${time} 患者自建` : '患者自建';
const creatorId = String(r?.creator || r?.creatorUserId || r?.createUserId || r?.executor || r?.executorUserId || '');
if (!creatorId) return time ? `创建时间:${time}` : '';
const name = resolveUserName(creatorId);
return time ? `${time} ${name}代建` : `${name}代建`;
}
function pickType(e) {
currentType.value = typeRange.value[e.detail.value] || { name: '全部', value: 'ALL' };
refreshList();
}
function pickTimeRange(val) {
if (Array.isArray(val)) {
dateRange.value = val;
} else if (val && typeof val === 'object' && Array.isArray(val.value)) {
dateRange.value = val.value;
} else if (val && typeof val === 'object' && val.detail && Array.isArray(val.detail.value)) {
dateRange.value = val.detail.value;
} else {
dateRange.value = [];
}
refreshList();
}
function clearTimeRange() {
dateRange.value = [];
refreshList();
}
function getFiles(r) {
const arr = r?.files;
return Array.isArray(arr)
? arr
.filter((i) => i && i.url)
.map((i) => ({ ...i, url: normalizeFileUrl(i.url) }))
: [];
}
function previewFiles(r, idx) {
const urls = getFiles(r).map((i) => i.url);
if (!urls.length) return;
uni.previewImage({ urls, current: urls[idx] });
}
function goAdd(t) {
if (!t?.templateType) return;
uni.navigateTo({
url: `/pages/case/visit-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&type=${encodeURIComponent(t.templateType)}&name=${encodeURIComponent(props.data?.name || '')}`,
});
}
function pickAddType(e) {
if (!props.archiveId) return toast('缺少档案信息');
const idx = Number(e?.detail?.value ?? -1);
const t = selectableTemplates.value[idx];
if (!t) return;
goAdd(t);
}
function showAddActionSheet() {
if (!props.archiveId) return toast('缺少档案信息');
const list = selectableTemplates.value;
if (!list.length) return toast('暂无可用病历模板');
uni.showActionSheet({
itemList: list.map((i) => i.name),
success: ({ tapIndex }) => {
const t = list[tapIndex];
if (!t) return;
goAdd(t);
},
fail: (e) => {
// 用户取消无需提示;其他错误忽略
const errMsg = String(e?.errMsg || '');
if (errMsg && errMsg.includes('cancel')) return;
if (errMsg) console.warn('[health-profile-tab] showActionSheet fail:', errMsg);
},
});
}
async function onFabTap() {
if (!props.archiveId) return toast('缺少档案信息');
// 模板可能尚未加载:先拉一次
if (!templates.value.length) {
loading('加载模板...');
try {
await loadVisitTemplates();
} finally {
hideLoading();
}
}
// <=6用 actionSheet>6由 picker 自己弹(这里不做额外处理)
if (useActionSheet.value) showAddActionSheet();
}
function edit(record) {
const type = String(record?.medicalType || record?.templateType || '') || '';
uni.navigateTo({
url: `/pages/case/visit-record-view?archiveId=${encodeURIComponent(props.archiveId)}&id=${encodeURIComponent(record._id)}&type=${encodeURIComponent(type)}`,
});
}
onMounted(() => {
uni.$on('archive-detail:visit-record-changed', refreshList);
});
onUnmounted(() => {
uni.$off('archive-detail:visit-record-changed', refreshList);
});
watch(
() => props.archiveId,
(v, old) => {
const next = v ? String(v) : '';
const prev = old ? String(old) : '';
if (next && next !== prev) refreshList();
},
{ immediate: true }
);
</script>
<style scoped>
.wrap {
padding: 24rpx 0 192rpx;
}
.filters {
display: flex;
gap: 20rpx;
padding: 20rpx 28rpx;
background: #f5f6f8;
border-bottom: 2rpx solid #f2f2f2;
}
.filter-pill {
background: #fff;
border: 2rpx solid #e6e6e6;
border-radius: 12rpx;
padding: 20rpx 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 20rpx;
flex: 1;
}
.pill-text {
font-size: 26rpx;
color: #333;
max-width: 360rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pill-icons {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.pill-clear {
display: flex;
align-items: center;
justify-content: center;
}
.share-tip {
padding: 20rpx 28rpx 0;
}
.share-tip-text {
display: inline-block;
background: #eef2ff;
color: #4338ca;
font-size: 24rpx;
padding: 12rpx 20rpx;
border-radius: 12rpx;
}
.list {
padding: 0 28rpx;
}
.card {
background: #fff;
border-radius: 20rpx;
margin-top: 20rpx;
overflow: hidden;
box-shadow: 0 12rpx 28rpx rgba(0, 0, 0, 0.06);
}
.record {
padding: 0;
}
.record-head {
display: flex;
align-items: center;
padding: 24rpx 24rpx 20rpx;
gap: 16rpx;
}
.record-title {
font-size: 30rpx;
font-weight: 600;
color: #1f1f1f;
}
.record-date {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.record-body {
padding: 0 24rpx 24rpx;
}
.line {
display: flex;
padding-top: 20rpx;
font-size: 26rpx;
color: #333;
line-height: 36rpx;
}
.line-label {
flex-shrink: 0;
color: #666;
}
.line-value {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.thumbs {
padding-top: 20rpx;
display: flex;
gap: 20rpx;
align-items: center;
flex-wrap: wrap;
}
.thumb {
width: 168rpx;
height: 128rpx;
border-radius: 12rpx;
overflow: hidden;
background: #f3f4f6;
border: 2rpx solid #e5e7eb;
}
.thumb-img {
width: 168rpx;
height: 128rpx;
}
.thumb-more {
font-size: 24rpx;
color: #6b7280;
}
.record-foot {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 24rpx;
border-top: 2rpx solid #f2f2f2;
font-size: 24rpx;
color: #999;
}
.foot-left {
flex-shrink: 0;
margin-right: 20rpx;
}
.record-tag {
font-size: 24rpx;
color: #fff;
padding: 8rpx 16rpx;
border-radius: 16rpx;
}
.bg-blue {
background: #0877F1;
}
.bg-amber {
background: #d97706;
}
.bg-teal {
background: #0f766e;
}
.bg-indigo {
background: #4f46e5;
}
.bg-green {
background: #16a34a;
}
.bg-rose {
background: #f43f5e;
}
.empty {
padding: 240rpx 0;
text-align: center;
color: #9aa0a6;
font-size: 26rpx;
}
.fab-picker {
position: fixed;
right: 32rpx;
width: 104rpx;
height: 104rpx;
z-index: 20;
}
.fab {
width: 104rpx;
height: 104rpx;
border-radius: 52rpx;
background: #0877F1;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 20rpx 36rpx rgba(79, 110, 247, 0.35);
}
.fab--disabled {
opacity: 0.5;
}
</style>