948 lines
26 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/service-info/service-info.vue -->
<view class="wrap">
<view class="filters">
<picker class="filter-item" mode="selector" :range="typeList" range-key="label" @change="pickType">
<view class="filter-pill">
<view class="pill-text" :class="{ muted: currentType.value === 'ALL' }">{{ currentType.value === 'ALL' ? '服务类型' : currentType.label }}</view>
<uni-icons type="arrowdown" size="12" color="#666" />
</view>
</picker>
<uni-datetime-picker class="filter-item wide-item" v-model="dateRange" type="daterange" rangeSeparator="-" @change="changeDates">
<view class="filter-pill wide">
<view class="pill-text" :class="{ muted: !dateRange.length }">
{{ dateRange.length ? dateRange.join('~') : '服务时间' }}
</view>
<view class="pill-icon" @click.stop="clearDates">
<uni-icons v-if="dateRange.length" type="closeempty" size="14" color="#666" />
<uni-icons v-else type="arrowdown" size="12" color="#666" />
</view>
</view>
</uni-datetime-picker>
<picker class="filter-item" mode="selector" :range="teamList" range-key="label" @change="pickTeam">
<view class="filter-pill">
<view class="pill-text">{{ currentTeam.label }}</view>
<uni-icons type="arrowdown" size="12" color="#666" />
</view>
</picker>
</view>
<view class="timeline">
<view v-for="(i, idx) in list" :key="i._id" class="cell" @click="toggleExpand(i)">
<view class="head">
<view class="dot"></view>
<view class="time">{{ i.timeStr }}</view>
<view v-if="showFileEntry && i.hasFile" class="file-link" @click.stop="viewFile(i)">
{{ i.fileType === 'article' ? '查看文章' : '查看问卷' }}
</view>
</view>
<view class="meta">
<view class="tag">{{ i.typeStr }}</view>
<view class="meta-text">{{ executorText(i) }}</view>
<view class="meta-text truncate">{{ executeTeamText(i) }}</view>
</view>
<view class="body">
<view class="content" :class="{ clamp: !expandMap[i._id] }">
{{ displayTaskContent(i) || '暂无内容' }}
</view>
<image class="pen" src="/static/icons/icon-pen.svg" @click.stop="edit(i)" />
</view>
<view v-if="idx < list.length - 1" class="line"></view>
</view>
<view v-if="list.length === 0" class="empty">暂无数据</view>
<uni-load-more
v-if="list.length"
:status="moreStatus"
:contentText="loadMoreText"
@clickLoadMore="getMore"
/>
</view>
<view class="fab" :style="{ bottom: `${floatingBottom}px` }" @click="add">
<uni-icons type="plusempty" size="24" color="#fff" />
</view>
<uni-popup ref="filePopupRef" type="bottom" :mask-click="true">
<view class="popup">
<view class="popup-title">
<view class="popup-title-text">{{ fileTitle }}</view>
<view class="popup-close" @click="closeFilePopup">
<uni-icons type="closeempty" size="18" color="#666" />
</view>
</view>
<view class="popup-body2">
<view class="desc">{{ fileDesc }}</view>
<button class="btn primary" @click="copyFile">{{ fileAction }}</button>
</view>
</view>
</uni-popup>
<!-- 编辑服务内容 -->
<uni-popup ref="editPopupRef" type="bottom" :mask-click="true" @maskClick="closeEditPopup">
<view class="edit-sheet">
<view class="edit-header">
<view class="edit-header-left" />
<view class="edit-title">修改服务内容</view>
<view class="edit-close" @click="closeEditPopup">
<uni-icons type="closeempty" size="18" color="#333" />
</view>
</view>
<view class="edit-body">
<textarea
v-model="editContent"
class="edit-textarea"
placeholder="请输入服务内容"
:maxlength="1000"
/>
<view class="counter">{{ (editContent || '').length }}/1000</view>
</view>
<view class="edit-footer">
<button class="btn primary" @click="saveEdit">保存</button>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import dayjs from 'dayjs';
import { storeToRefs } from 'pinia';
import api from '@/utils/api';
import useAccountStore from '@/store/account';
import { toast } from '@/utils/widget';
import { getServiceTypeLabel, getServiceTypeOptions } from '@/utils/service-type-const';
const props = defineProps({
data: { type: Object, default: () => ({}) },
archiveId: { type: String, default: '' },
reachBottomTime: { type: [String, Number], default: '' },
floatingBottom: { type: Number, default: 16 },
});
const typeList = [{ label: '全部', value: 'ALL' }, ...getServiceTypeOptions({ excludeCustomerUpdate: true })];
const teamList = ref([{ label: '全部', value: 'ALL' }]);
const currentType = ref(typeList[0]);
const currentTeam = ref(teamList.value[0]);
const dateRange = ref([]);
const page = ref(1);
const pageSize = 10;
const pages = ref(1);
const loading = ref(false);
const list = ref([]);
const expandMap = ref({});
const userNameMap = ref({});
const teamNameMap = ref({});
const loadedTeamNameIds = new Set();
// 先隐藏“查看问卷/文章”入口(保留相关代码,后续可随时打开)
const showFileEntry = ref(false);
const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
const { getDoctorInfo } = accountStore;
async function ensureDoctor() {
if (doctorInfo.value) return;
if (!account.value?.openid) return;
try {
await getDoctorInfo();
} catch {
// ignore
}
}
function getUserId() {
const d = doctorInfo.value || {};
const a = account.value || {};
const t = uni.getStorageSync('ykt_case_current_team') || {};
return String(d.userid || d.userId || d.corpUserId || a.userid || a.userId || t.userId || t.userid || t.corpUserId || '') || '';
}
function getCorpId() {
const t = uni.getStorageSync('ykt_case_current_team') || {};
const a = account.value || {};
const d = doctorInfo.value || {};
return String(t.corpId || a.corpId || d.corpId || '') || '';
}
function getCurrentTeamId() {
const t = uni.getStorageSync('ykt_case_current_team') || {};
return String(t.teamId || '') || '';
}
function normalizeName(v) {
const s = v === 0 ? '0' : v ? String(v) : '';
const trimmed = s.trim();
if (!trimmed) return '';
if (['-', '—', '--'].includes(trimmed)) return '';
return trimmed;
}
function getExecutorId(r) {
const row = r && typeof r === 'object' ? r : {};
return String(
row.executorUserId ||
row.executorUserID ||
row.executorId ||
row.executor ||
row.creatorUserId ||
row.creator ||
row.updateUserId ||
''
) || '';
}
function executorText(r) {
const row = r && typeof r === 'object' ? r : {};
const uid = getExecutorId(row);
const mapped = normalizeName(resolveUserName(uid));
const fromRow = normalizeName(row.executorName || row.executorUserName || row.creatorName || row.updateUserName || '');
if (mapped && uid && mapped !== uid && (!fromRow || fromRow === uid)) return mapped;
return fromRow || mapped || (uid ? uid : '--');
}
function getExecuteTeamId(r) {
const row = r && typeof r === 'object' ? r : {};
return String(row.executeTeamId || row.teamId || row.teamID || '') || '';
}
function resolveTeamName(teamId) {
const tid = String(teamId || '') || '';
if (!tid) return '';
const cached = teamNameMap.value?.[tid];
if (cached) return String(cached);
const list = teamList.value || [];
const hit = list.find((i) => i && i.value === tid);
if (hit?.label) return String(hit.label);
// 不阻塞渲染:后台补齐团队名
void loadTeamName(tid);
return '';
}
async function loadTeamName(teamId) {
const tid = String(teamId || '') || '';
if (!tid) return;
if (loadedTeamNameIds.has(tid)) return;
const corpId = getCorpId();
if (!corpId) return;
loadedTeamNameIds.add(tid);
try {
const res = await api('getTeamBaseInfo', { corpId, teamId: tid });
if (res?.success) {
const data = res?.data && typeof res.data === 'object' ? res.data : {};
const name = String(data?.name || data?.teamName || data?.team || '').trim();
if (name) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name };
return;
}
} catch {
// ignore
}
// 兜底:用 getTeamData 再试一次
try {
const res = await api('getTeamData', { corpId, teamId: tid });
if (!res?.success) return;
const data = res?.data && typeof res.data === 'object' ? res.data : {};
const name = String(data?.name || data?.teamName || data?.team || '').trim();
if (name) teamNameMap.value = { ...(teamNameMap.value || {}), [tid]: name };
} catch {
// ignore
}
}
function executeTeamText(r) {
const row = r && typeof r === 'object' ? r : {};
const fromRow = normalizeName(row.executeTeamName || row.teamName || '');
if (fromRow) return fromRow;
const tid = getExecuteTeamId(row) || getCurrentTeamId();
const mapped = normalizeName(resolveTeamName(tid));
return mapped || (tid ? tid : '--');
}
function resolveUserName(userId) {
const id = String(userId || '');
if (!id) return '';
const map = userNameMap.value || {};
return String(map[id] || '') || id;
}
function escapeRegExp(str) {
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function replaceKnownUserIds(text) {
const map = userNameMap.value || {};
const ids = Object.keys(map);
if (!ids.length) return text;
let out = text;
ids.forEach((id) => {
const name = String(map[id] || '');
if (!id || !name || name === id) return;
const re = new RegExp(`(^|[^0-9A-Za-z_])(${escapeRegExp(id)})(?=[^0-9A-Za-z_]|$)`, 'g');
out = out.replace(re, (_, p1) => `${p1}${name}`);
});
return out;
}
function formatTaskContent(text) {
if (typeof text !== 'string') return '';
const withPlaceholders = text.replace(/&&&([^&]+)&&&/g, (_, id) => resolveUserName(id));
return replaceKnownUserIds(withPlaceholders).trim();
}
function displayTaskContent(r) {
return formatTaskContent(String(r?.taskContent || ''));
}
const loadedTeamMemberIds = new Set();
async function loadTeamMembers(teamId) {
const tid = String(teamId || '') || '';
if (!tid) return;
if (loadedTeamMemberIds.has(tid)) return;
await ensureDoctor();
const corpId = getCorpId();
if (!corpId) return;
loadedTeamMemberIds.add(tid);
// 以 getTeamData 为准getTeamMemberAvatarsAndName 存在不全/不准的情况)
const fallback = await api('getTeamData', { corpId, teamId: tid });
if (!fallback?.success) {
loadedTeamMemberIds.delete(tid);
return;
}
const t = fallback?.data && typeof fallback.data === 'object' ? fallback.data : {};
const members = Array.isArray(t.memberList) ? t.memberList : [];
const map = members.reduce((acc, m) => {
if (typeof m === 'string') {
const id = String(m || '');
if (id) acc[id] = id;
return acc;
}
const uid = String(m?.userid || m?.userId || m?.corpUserId || m?._id || m?.id || '');
if (!uid) return acc;
acc[uid] = String(m?.anotherName || m?.name || m?.userid || m?.userId || '') || uid;
return acc;
}, {});
userNameMap.value = { ...(userNameMap.value || {}), ...map };
// 补缺:仅当当前没有映射时才用 avatars 接口补齐,避免覆盖正确姓名
try {
const res = await api('getTeamMemberAvatarsAndName', { corpId, teamId: tid });
if (res?.success && res?.data && typeof res.data === 'object') {
const raw = res.data;
const patch = Object.keys(raw).reduce((acc, uid) => {
const id = String(uid || '');
if (!id) return acc;
const existing = userNameMap.value?.[id];
if (existing && existing !== id) return acc;
const name = String(raw?.[uid]?.name || raw?.[uid]?.anotherName || '').trim();
if (!name || name === id) return acc;
acc[id] = name;
return acc;
}, {});
if (Object.keys(patch).length) userNameMap.value = { ...(userNameMap.value || {}), ...patch };
}
} catch {
// ignore
}
}
let corpMemberBatchInflight = null; // Promise | 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 moreStatus = computed(() => {
if (loading.value) return 'loading';
return page.value <= pages.value ? 'more' : 'no-more';
});
const loadMoreText = {
contentdown: '点击加载更多',
contentrefresh: '加载中...',
contentnomore: '没有更多了',
};
function mapRow(i) {
const hasFile = Boolean(i.pannedEventSendFile);
const fileType = i.pannedEventSendFile?.type === 'article' ? 'article' : i.pannedEventSendFile?.type === 'questionnaire' ? 'questionnaire' : '';
return {
...i,
_id: String(i?._id || i?.id || ''),
executorUserId: getExecutorId(i),
executeTeamId: getExecuteTeamId(i),
hasFile,
fileType,
timeStr: i.executionTime ? dayjs(i.executionTime).format('YYYY-MM-DD HH:mm') : '--',
typeStr: getServiceTypeLabel(i.eventType),
};
}
function reset() {
page.value = 1;
pages.value = 1;
list.value = [];
getMore();
}
function getEventTypeList() {
const v = currentType.value?.value || 'ALL';
if (v === 'ALL') return typeList.filter((i) => i.value !== 'ALL').map((i) => i.value);
return [v];
}
async function getMore() {
if (!props.archiveId) return;
if (loading.value) return;
if (page.value > pages.value) return;
loading.value = true;
try {
await ensureDoctor();
const corpId = getCorpId();
if (!corpId) {
toast('缺少 corpId请先完成登录/团队选择');
return;
}
const params = {
corpId,
customerId: String(props.archiveId),
eventTypeList: getEventTypeList(),
};
if (dateRange.value.length) params.executionTime = dateRange.value;
const teamId = currentTeam.value?.value && currentTeam.value.value !== 'ALL' ? currentTeam.value.value : '';
const res = await api('getServiceRecord', {
page: page.value,
pageSize,
params,
queryType: '',
teamId,
});
if (!res?.success) {
toast(res?.message || '获取服务记录失败');
return;
}
const payload = res?.data;
const arr = Array.isArray(payload?.data) ? payload.data : Array.isArray(payload) ? payload : [];
const p =
typeof payload?.pages === 'number'
? payload.pages
: typeof payload?.total === 'number'
? Math.ceil(payload.total / pageSize) || 0
: typeof res?.total === 'number'
? Math.ceil(res.total / pageSize) || 0
: arr.length < pageSize
? page.value
: page.value + 1;
pages.value = p;
const mapped = arr.map(mapRow).filter((i) => i && i._id);
list.value = page.value === 1 ? mapped : [...list.value, ...mapped];
page.value += 1;
// 尽量加载记录所属团队成员,用于执行人展示
const teamIds = mapped.map((i) => i.executeTeamId).filter(Boolean);
Array.from(new Set(teamIds)).forEach((tid) => loadTeamMembers(tid));
// 补齐非团队成员执行人姓名(例如其他团队创建/操作)
const executorIds = mapped.map((i) => i.executorUserId).filter(Boolean);
void batchLoadCorpMembers(executorIds);
} finally {
loading.value = false;
}
}
function toggleExpand(r) {
expandMap.value[r._id] = !expandMap.value[r._id];
}
function pickType(e) {
currentType.value = typeList[e.detail.value] || typeList[0];
reset();
}
function pickTeam(e) {
currentTeam.value = teamList.value[e.detail.value] || teamList.value[0];
const tid = currentTeam.value?.value && currentTeam.value.value !== 'ALL' ? currentTeam.value.value : getCurrentTeamId();
loadTeamMembers(tid);
reset();
}
function changeDates() {
reset();
}
function clearDates() {
if (!dateRange.value.length) return;
dateRange.value = [];
reset();
}
function add() {
const archive = props.data || {};
const customerUserId = String(archive.externalUserId || archive.customerUserId || '') || '';
uni.setStorageSync('service-record-detail', {
customerId: String(props.archiveId),
customerName: String(archive.name || ''),
customerUserId,
eventType: '',
});
uni.navigateTo({ url: `/pages/case/service-record-detail?archiveId=${encodeURIComponent(props.archiveId)}&mode=add` });
}
function edit(record) {
if (!record?._id) return;
editingRecord.value = record;
editContent.value = String(record?.taskContent || '') || '';
editPopupRef.value?.open?.();
}
const filePopupRef = ref(null);
const fileTitle = ref('');
const fileDesc = ref('');
const fileAction = ref('');
const fileCopyText = ref('');
function viewFile(record) {
if (!record.hasFile) return;
if (record.fileType === 'article') {
fileTitle.value = '查看文章';
fileDesc.value = record.pannedEventSendFile?.url || '';
fileAction.value = '复制链接';
fileCopyText.value = record.pannedEventSendFile?.url || '';
} else {
fileTitle.value = '查看问卷';
fileDesc.value = `问卷ID: ${record.pannedEventSendFile?.surveryId || '--'}`;
fileAction.value = '复制问卷ID';
fileCopyText.value = record.pannedEventSendFile?.surveryId || '';
}
filePopupRef.value?.open?.();
}
function closeFilePopup() {
filePopupRef.value?.close?.();
}
function copyFile() {
if (!fileCopyText.value) return;
uni.setClipboardData({
data: fileCopyText.value,
success: () => uni.showToast({ title: '已复制', icon: 'success' }),
});
closeFilePopup();
}
const editPopupRef = ref(null);
const editingRecord = ref(null);
const editContent = ref('');
function closeEditPopup() {
editPopupRef.value?.close?.();
editingRecord.value = null;
}
async function saveEdit() {
const r = editingRecord.value;
if (!r?._id) return;
if (!String(editContent.value || '').trim()) return toast('请输入服务内容');
await ensureDoctor();
const corpId = getCorpId();
const userId = getUserId();
if (!corpId || !userId) return toast('缺少用户信息');
const res = await api('updateServiceRecord', {
corpId,
id: String(r._id),
params: {
taskContent: String(editContent.value || ''),
updateUserId: userId,
},
});
if (!res?.success) return toast(res?.message || '修改失败');
// 本地同步,避免闪烁
const idx = list.value.findIndex((i) => i && i._id === String(r._id));
if (idx > -1) list.value[idx] = { ...list.value[idx], taskContent: String(editContent.value || '') };
uni.$emit('archive-detail:service-record-changed');
uni.showToast({ title: '保存成功', icon: 'success' });
closeEditPopup();
}
async function loadTeams() {
await ensureDoctor();
const corpId = getCorpId();
const userId = getUserId();
if (!corpId || !userId) return;
const res = await api('getTeamBymember', { corpId, corpUserId: userId });
if (!res?.success) return;
const list = Array.isArray(res?.data) ? res.data : Array.isArray(res?.data?.data) ? res.data.data : [];
const normalized = list
.map((raw) => {
if (!raw || typeof raw !== 'object') return null;
const teamId = raw.teamId || raw.id || raw._id || '';
const name = raw.name || raw.teamName || raw.team || '';
if (!teamId || !name) return null;
return { label: String(name), value: String(teamId) };
})
.filter(Boolean);
teamList.value = [{ label: '全部', value: 'ALL' }, ...normalized];
currentTeam.value = teamList.value.find((i) => i.value === currentTeam.value?.value) || teamList.value[0];
}
onMounted(() => {
loadTeams();
loadTeamMembers(getCurrentTeamId());
reset();
uni.$on('archive-detail:service-record-changed', reset);
});
onUnmounted(() => {
uni.$off('archive-detail:service-record-changed', reset);
});
watch(
() => props.reachBottomTime,
() => getMore()
);
</script>
<style scoped>
.wrap {
padding: 16rpx 0 192rpx;
}
.filters {
padding: 20rpx 28rpx;
background: #fff;
border-bottom: 2rpx solid #f0f0f0;
display: flex;
align-items: center;
gap: 24rpx;
}
.filter-item {
display: block;
flex: 1;
min-width: 0;
}
.filter-item.wide-item {
flex: 1.4;
}
/* Removed old deep selectors */
.filter-pill {
background: #f7f8fa;
border: 2rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 20rpx 24rpx;
display: flex;
align-items: center;
gap: 16rpx;
width: 100%;
min-width: 0;
box-sizing: border-box; /* Ensure padding doesn't overflow width */
}
.pill-text {
font-size: 26rpx;
color: #333;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
}
.pill-text.muted {
color: #999;
font-weight: 400;
}
.pill-icon {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.filter-pill :deep(uni-icons) {
flex-shrink: 0;
}
.timeline {
background: #fff;
margin-top: 12rpx;
padding: 20rpx 0 140rpx;
}
.cell {
padding: 0 28rpx;
position: relative;
}
.head {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding-left: 36rpx;
}
.dot {
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #0877F1;
}
.time {
font-size: 28rpx;
font-weight: 600;
color: #1f1f1f;
}
.file-link {
font-size: 26rpx;
color: #0877F1;
}
.meta {
display: flex;
align-items: center;
gap: 20rpx;
padding-left: 36rpx;
margin-bottom: 12rpx;
}
.tag {
font-size: 24rpx;
color: #0877F1;
border: 2rpx solid #0877F1;
border-radius: 999rpx;
padding: 8rpx 16rpx;
}
.meta-text {
font-size: 26rpx;
color: #333;
}
.truncate {
max-width: 320rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.body {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding-left: 36rpx;
padding-bottom: 24rpx;
}
.content {
flex: 1;
font-size: 26rpx;
color: #666;
line-height: 36rpx;
margin-right: 20rpx;
}
.content.clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
}
.pen {
width: 36rpx;
height: 36rpx;
margin-top: 4rpx;
}
.line {
position: absolute;
left: 36rpx;
top: 68rpx;
bottom: 0;
width: 4rpx;
background: #0877F1;
opacity: 0.6;
}
.empty {
padding: 240rpx 0;
text-align: center;
color: #9aa0a6;
font-size: 26rpx;
}
.fab {
position: fixed;
right: 32rpx;
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);
z-index: 20;
}
.popup {
background: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
overflow: hidden;
}
.popup-title {
position: relative;
padding: 28rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.popup-title-text {
text-align: center;
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.popup-close {
position: absolute;
right: 24rpx;
top: 0;
height: 100%;
display: flex;
align-items: center;
}
.popup-body2 {
padding: 28rpx;
}
.desc {
font-size: 28rpx;
color: #333;
line-height: 40rpx;
word-break: break-all;
margin-bottom: 28rpx;
}
.btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
border-radius: 12rpx;
font-size: 30rpx;
}
.btn::after {
border: none;
}
.btn.primary {
background: #0877F1;
color: #fff;
}
.edit-sheet {
background: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
overflow: hidden;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
}
.edit-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx;
border-bottom: 2rpx solid #f0f0f0;
}
.edit-header-left {
width: 36rpx;
height: 36rpx;
}
.edit-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.edit-close {
width: 36rpx;
height: 36rpx;
display: flex;
align-items: center;
justify-content: center;
}
.edit-body {
padding: 28rpx 28rpx 0;
}
.edit-textarea {
width: 100%;
height: 260rpx;
padding: 20rpx;
border: 2rpx solid #e5e7eb;
border-radius: 12rpx;
font-size: 28rpx;
box-sizing: border-box;
}
.counter {
margin-top: 12rpx;
text-align: right;
font-size: 24rpx;
color: #999;
}
.edit-footer {
padding: 24rpx 28rpx 0;
}
</style>