OfficeSystem/pages/customer/follow/edit/index.vue

1214 lines
32 KiB
Vue
Raw Normal View History

2025-11-10 14:14:49 +08:00
<template>
<view class="edit-followup-page">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="navbar-content">
<text class="nav-btn" @click="handleCancel"></text>
<text class="nav-title">修改跟进记录</text>
<text class="nav-btn" style="opacity: 0;">占位</text>
</view>
</view>
<!-- 加载状态 -->
<view class="loading-container" v-if="loading">
<text class="loading-text">加载中...</text>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y v-else>
<view class="form-container">
<!-- 图片上传 -->
<view class="form-section">
<view class="section-label">图片</view>
<view class="upload-area" @click="chooseImages">
<view class="upload-placeholder">
<text class="upload-icon">+</text>
</view>
</view>
<view class="upload-tips">
<text>请上传大小不超过200MB格式为png/jpg/jpeg 的文件</text>
</view>
<!-- 图片预览 -->
<view class="images-preview" v-if="formData.pictures.length > 0">
<view
class="image-item"
v-for="(image, index) in formData.pictures"
:key="index"
>
<image :src="image" mode="aspectFill" class="preview-image" @click="previewImage(index)" />
<view class="remove-btn" @click.stop="removeImage(index)"></view>
</view>
</view>
</view>
<!-- 客户信息 -->
<view class="form-section">
<!-- 客户 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">客户</text>
2025-11-10 14:14:49 +08:00
<view class="form-input-wrapper readonly">
<input
v-model="formData.customerName"
class="form-input"
placeholder="客户信息"
disabled
placeholder-style="color: #999;"
/>
</view>
</view>
<!-- 客户状态 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">客户状态</text>
2025-11-10 14:14:49 +08:00
<view class="form-input-wrapper" @click="openStatusPicker">
<input
:value="getStatusText(formData.customerStatus)"
class="form-input"
placeholder="请选择客户状态"
disabled
placeholder-style="color: #999;"
/>
<text class="arrow"></text>
</view>
</view>
<!-- 意向强度 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">意向强度</text>
2025-11-10 14:14:49 +08:00
<view class="form-input-wrapper" @click="openIntentLevelPicker">
<input
:value="getIntentLevelText(formData.customerIntentLevel)"
class="form-input"
placeholder="请选择意向强度"
disabled
placeholder-style="color: #999;"
/>
<text class="arrow"></text>
</view>
</view>
<!-- 跟进方式 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">跟进方式</text>
2025-11-10 14:14:49 +08:00
<view class="form-input-wrapper" @click="openFollowTypePicker">
<input
:value="getFollowTypeText(formData.type)"
class="form-input"
placeholder="请选择跟进方式"
disabled
placeholder-style="color: #999;"
/>
<text class="arrow"></text>
</view>
</view>
<!-- 跟进内容 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">跟进内容</text>
2025-11-10 14:14:49 +08:00
<view class="textarea-wrapper">
<textarea
v-model="formData.content"
class="form-textarea"
placeholder="请输入跟进内容"
placeholder-style="color: #999;"
:maxlength="1000"
auto-height
/>
<view class="char-count">{{ formData.content.length }}/1000</view>
</view>
</view>
</view>
<!-- 附件列表 -->
<view class="form-section">
<view class="section-label">附件列表</view>
<view class="upload-area" @click="chooseFiles">
<view class="upload-placeholder">
<text class="upload-icon">+</text>
</view>
</view>
<!-- 文件列表 -->
<view class="files-list" v-if="formData.attaches.length > 0">
<view
class="file-item"
v-for="(file, index) in formData.attaches"
:key="index"
@click="previewFile(file)"
>
<text class="file-icon">{{ getFileIcon(file.name || file.path) }}</text>
<view class="file-info">
<text class="file-name">{{ file.name || getFileNameFromUrl(file.path) }}</text>
<text class="file-size" v-if="file.size > 0">{{ formatFileSize(file.size) }}</text>
</view>
<view class="remove-btn" @click.stop="removeFile(index)"></view>
</view>
</view>
</view>
<!-- 时间信息 -->
<view class="form-section">
<!-- 跟进时间 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">跟进时间</text>
2025-11-10 14:14:49 +08:00
<view class="form-input-wrapper" @click="openFollowTimePicker">
<input
:value="formatDateTime(formData.followTime)"
class="form-input"
placeholder="请选择跟进时间"
disabled
placeholder-style="color: #999;"
/>
<text class="arrow"></text>
</view>
</view>
<!-- 下次跟进 -->
<view class="form-item">
2025-11-10 16:54:46 +08:00
<text class="form-label required">下次跟进</text>
2025-11-10 14:14:49 +08:00
<view class="form-input-wrapper" @click="openNextFollowTimePicker">
<input
:value="formatDateTime(formData.nextFollowTime)"
class="form-input"
placeholder="请选择下次跟进时间"
disabled
placeholder-style="color: #999;"
/>
<text class="arrow"></text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 选择器组件 -->
<FollowupPickers
:show-status-picker="showStatusPicker"
:show-intent-level-picker="showIntentLevelPicker"
:show-follow-type-picker="showFollowTypePicker"
:status-options="statusOptions"
:intent-level-options="intentLevelOptions"
:follow-type-options="followTypeOptions"
:form-data="formData"
@update:form-data="formData = $event"
@close-picker="handleClosePicker"
/>
<!-- 跟进时间选择器 -->
<uv-datetime-picker
ref="followTimePickerRef"
v-model="followTimeValue"
mode="datetime"
@confirm="onFollowTimeConfirm"
></uv-datetime-picker>
<!-- 下次跟进时间选择器 -->
<uv-datetime-picker
ref="nextFollowTimePickerRef"
v-model="nextFollowTimeValue"
mode="datetime"
@confirm="onNextFollowTimeConfirm"
></uv-datetime-picker>
<!-- 底部按钮 -->
<view class="button-wrapper">
<view class="button-group">
<uv-button
type="primary"
:text="submitting ? '提交中...' : '确定'"
:disabled="!canSubmit || submitting"
:loading="submitting"
loadingText="提交中..."
@click="handleSubmit"
></uv-button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { chooseAndUploadImages, batchUploadFilesToQiniu } from '@/utils/qiniu.js';
import {
updateFollowup,
getFollowupDetail,
getCustomerStatusDict,
getCustomerIntentLevelDict,
getCustomerFollowTypeDict
2025-11-12 15:33:53 +08:00
} from '@/api/customer.js';
2025-11-12 15:18:21 +08:00
import FollowupPickers from '@/components/customer/followup-form/FollowupPickers.vue';
2025-11-11 12:00:18 +08:00
import {
getCustomerStatusText,
getIntentLevelText as getIntentLevelTextFromMapping,
getFollowTypeText as getFollowTypeTextFromMapping
} from '@/utils/customerMappings';
2025-11-10 14:14:49 +08:00
// 跟进ID
const followId = ref('');
// 表单数据
const formData = ref({
followId: '',
customerId: '',
customerName: '',
customerStatus: '',
customerIntentLevel: '',
type: '',
content: '',
pictures: [],
attaches: [],
followTime: '',
nextFollowTime: ''
});
// 弹窗状态
const showStatusPicker = ref(false);
const showIntentLevelPicker = ref(false);
const showFollowTypePicker = ref(false);
// 日期选择器 ref
const followTimePickerRef = ref(null);
const nextFollowTimePickerRef = ref(null);
// 日期选择器值(时间戳)
const followTimeValue = ref(Number(new Date()));
const nextFollowTimeValue = ref(Number(new Date()));
// 选项数据
const statusOptions = ref([]);
const intentLevelOptions = ref([]);
const followTypeOptions = ref([]);
// 提交状态
const submitting = ref(false);
// 加载状态
const loading = ref(false);
// 是否可以提交
const canSubmit = computed(() => {
return formData.value.followId &&
formData.value.customerId &&
formData.value.customerStatus &&
formData.value.customerIntentLevel &&
formData.value.type &&
formData.value.content.trim() &&
formData.value.followTime &&
formData.value.nextFollowTime;
});
// 页面加载
onLoad(async (options) => {
if (options.followId) {
followId.value = options.followId;
formData.value.followId = options.followId;
// 加载字典数据和跟进详情
await Promise.all([
loadDictData(),
loadFollowupDetail()
]);
} else {
uni.showToast({
title: '缺少跟进ID参数',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
}
});
// 加载字典数据
const loadDictData = async () => {
try {
const [statusRes, intentLevelRes, followTypeRes] = await Promise.all([
getCustomerStatusDict(),
getCustomerIntentLevelDict(),
getCustomerFollowTypeDict()
]);
if (statusRes) {
statusOptions.value = statusRes || [];
}
if (intentLevelRes) {
intentLevelOptions.value = intentLevelRes || [];
}
if (followTypeRes) {
followTypeOptions.value = followTypeRes || [];
}
} catch (err) {
console.error('加载字典数据失败:', err);
uni.showToast({
title: '加载字典数据失败',
icon: 'none'
});
}
};
// 加载跟进详情
const loadFollowupDetail = async () => {
if (!followId.value) return;
loading.value = true;
try {
const res = await getFollowupDetail(followId.value);
if (res) {
// 填充表单数据
formData.value.followId = res.followId || res.id || followId.value;
formData.value.customerId = res.customerId || '';
formData.value.customerName = res.customerName || '';
formData.value.customerStatus = res.customerStatus || '';
formData.value.customerIntentLevel = res.customerIntentLevel || '';
formData.value.type = res.type || '';
formData.value.content = res.content || '';
// 处理图片(可能是逗号分隔的字符串)
if (res.picture) {
if (typeof res.picture === 'string') {
formData.value.pictures = res.picture.split(',').map(url => url.trim()).filter(url => url);
} else if (Array.isArray(res.picture)) {
formData.value.pictures = res.picture;
}
} else if (res.pictures && Array.isArray(res.pictures)) {
formData.value.pictures = res.pictures;
}
// 处理附件(可能是逗号分隔的字符串)
if (res.attaches) {
if (typeof res.attaches === 'string') {
const attachUrls = res.attaches.split(',').map(url => url.trim()).filter(url => url);
formData.value.attaches = attachUrls.map(url => ({
path: url,
name: getFileNameFromUrl(url),
size: 0
}));
} else if (Array.isArray(res.attaches)) {
formData.value.attaches = res.attaches.map(item => {
if (typeof item === 'string') {
return {
path: item,
name: getFileNameFromUrl(item),
size: 0
};
}
return item;
});
}
}
// 处理时间
if (res.followTime) {
formData.value.followTime = res.followTime;
const followDate = new Date(res.followTime);
followTimeValue.value = Number(followDate);
}
if (res.nextFollowTime) {
formData.value.nextFollowTime = res.nextFollowTime;
const nextFollowDate = new Date(res.nextFollowTime);
nextFollowTimeValue.value = Number(nextFollowDate);
}
}
} catch (err) {
console.error('加载跟进详情失败:', err);
uni.showToast({
title: err.message || '加载跟进详情失败',
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} finally {
loading.value = false;
}
};
// 从URL中提取文件名
const getFileNameFromUrl = (url) => {
if (!url) return '未知文件';
try {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const fileName = pathname.split('/').pop();
// 如果文件名包含特殊字符,尝试解码
return decodeURIComponent(fileName) || '未知文件';
} catch (e) {
// 如果不是完整URL尝试从路径中提取
const parts = url.split('/');
const fileName = parts[parts.length - 1];
// 移除可能的查询参数
return fileName.split('?')[0] || '未知文件';
}
};
// 打开状态选择器
const openStatusPicker = () => {
showStatusPicker.value = true;
};
// 打开意向强度选择器
const openIntentLevelPicker = () => {
showIntentLevelPicker.value = true;
};
// 打开跟进方式选择器
const openFollowTypePicker = () => {
showFollowTypePicker.value = true;
};
// 关闭选择器
const handleClosePicker = (pickerType) => {
switch (pickerType) {
case 'status':
showStatusPicker.value = false;
break;
case 'intentLevel':
showIntentLevelPicker.value = false;
break;
case 'followType':
showFollowTypePicker.value = false;
break;
}
};
// 确认跟进时间
const onFollowTimeConfirm = (e) => {
const date = new Date(followTimeValue.value);
formData.value.followTime = formatDateTimeForInput(date);
};
// 确认下次跟进时间
const onNextFollowTimeConfirm = (e) => {
const date = new Date(nextFollowTimeValue.value);
formData.value.nextFollowTime = formatDateTimeForInput(date);
};
// 打开跟进时间选择器
const openFollowTimePicker = () => {
if (formData.value.followTime) {
const date = new Date(formData.value.followTime);
followTimeValue.value = Number(date);
}
followTimePickerRef.value?.open();
};
// 打开下次跟进时间选择器
const openNextFollowTimePicker = () => {
if (formData.value.nextFollowTime) {
const date = new Date(formData.value.nextFollowTime);
nextFollowTimeValue.value = Number(date);
}
nextFollowTimePickerRef.value?.open();
};
// 选择图片并自动上传
const chooseImages = async () => {
try {
const remainingCount = 9 - formData.value.pictures.length;
if (remainingCount <= 0) {
uni.showToast({
title: '最多只能添加9张图片',
icon: 'none'
});
return;
}
const urls = await chooseAndUploadImages({
count: remainingCount,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera']
});
formData.value.pictures = [...formData.value.pictures, ...urls];
} catch (err) {
console.error('选择或上传图片失败:', err);
uni.showToast({
title: err.message || '选择图片失败',
icon: 'none'
});
}
};
// 预览图片
const previewImage = (index) => {
uni.previewImage({
urls: formData.value.pictures,
current: index
});
};
// 删除图片
const removeImage = (index) => {
formData.value.pictures.splice(index, 1);
};
// 选择文件(参考添加页面的实现)
const chooseFiles = async () => {
const remainingCount = 10 - formData.value.attaches.length;
if (remainingCount <= 0) {
uni.showToast({
title: '最多只能添加10个文件',
icon: 'none'
});
return;
}
// 优先使用 uni.chooseFileH5和部分平台支持
// #ifdef H5 || MP-WEIXIN || APP-PLUS
try {
uni.chooseFile({
count: remainingCount,
extension: ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx', '.txt', '.zip', '.rar', '.7z', '.jpg', '.png', '.jpeg', '.gif', '.bmp', '.webp', '.ico', '.mp3', '.wav', '.m4a', '.ogg', '.flac', '.aac', '.mp4', '.avi', '.mov', '.wmv', '.flv', '.mpeg', '.mpg', '.m4v', '.webm', '.mkv', '.exe'],
success: async (res) => {
try {
uni.showLoading({
title: '上传中...',
mask: true
});
// 批量上传文件到七牛云
const uploadResults = await batchUploadFilesToQiniu(
res.tempFiles.map(file => ({
path: file.path,
name: file.name
}))
);
// 将上传结果添加到文件列表
const newFiles = uploadResults.map(result => ({
name: result.name,
path: result.url,
size: result.size
}));
formData.value.attaches = [...formData.value.attaches, ...newFiles];
uni.hideLoading();
uni.showToast({
title: `成功添加${newFiles.length}个文件`,
icon: 'success'
});
} catch (error) {
uni.hideLoading();
console.error('上传文件失败:', error);
uni.showToast({
title: error.message || '上传文件失败',
icon: 'none'
});
}
},
fail: (err) => {
console.error('选择文件失败:', err);
chooseFilesNative();
}
});
} catch (error) {
chooseFilesNative();
}
// #endif
// #ifndef H5 || MP-WEIXIN || APP-PLUS
chooseFilesNative();
// #endif
};
// 原生文件选择方法(安卓平台)
const chooseFilesNative = async () => {
const remainingCount = 10 - formData.value.attaches.length;
if (typeof plus !== 'undefined') {
try {
const Intent = plus.android.importClass('android.content.Intent');
const main = plus.android.runtimeMainActivity();
const intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType('*/*');
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
main.startActivityForResult(intent, 1001);
const originalOnActivityResult = main.onActivityResult;
main.onActivityResult = async (requestCode, resultCode, data) => {
if (requestCode === 1001) {
if (resultCode === -1 && data) {
try {
const clipData = data.getClipData();
const files = [];
const getFileName = (uri) => {
try {
const cursor = main.getContentResolver().query(uri, null, null, null, null);
if (cursor && cursor.moveToFirst()) {
const nameIndex = cursor.getColumnIndex('_display_name');
if (nameIndex !== -1) {
const fileName = cursor.getString(nameIndex);
cursor.close();
return fileName;
}
cursor.close();
}
} catch (e) {
console.error('获取文件名失败:', e);
}
return null;
};
if (clipData) {
const count = clipData.getItemCount();
for (let i = 0; i < count && files.length < remainingCount; i++) {
const item = clipData.getItemAt(i);
const uri = item.getUri();
const uriString = uri.toString();
let fileName = getFileName(uri) || `file_${Date.now()}_${i}`;
files.push({
name: fileName,
path: uriString,
size: 0
});
}
} else {
const uri = data.getData();
if (uri) {
const uriString = uri.toString();
let fileName = getFileName(uri) || `file_${Date.now()}`;
files.push({
name: fileName,
path: uriString,
size: 0
});
}
}
if (files.length > 0) {
uni.showLoading({
title: '上传中...',
mask: true
});
try {
const uploadResults = await batchUploadFilesToQiniu(files);
const newFiles = uploadResults.map(result => ({
name: result.name,
path: result.url,
size: result.size
}));
formData.value.attaches = [...formData.value.attaches, ...newFiles];
uni.hideLoading();
uni.showToast({
title: `成功添加${newFiles.length}个文件`,
icon: 'success'
});
} catch (uploadError) {
uni.hideLoading();
console.error('上传文件失败:', uploadError);
uni.showToast({
title: uploadError.message || '上传文件失败',
icon: 'none'
});
}
}
if (originalOnActivityResult) {
main.onActivityResult = originalOnActivityResult;
}
} catch (error) {
uni.hideLoading();
console.error('处理文件选择结果失败:', error);
uni.showToast({
title: '处理文件失败',
icon: 'none'
});
}
}
} else {
if (originalOnActivityResult) {
originalOnActivityResult(requestCode, resultCode, data);
}
}
};
} catch (error) {
console.error('打开文件选择器失败:', error);
uni.showToast({
title: '文件选择功能暂不可用',
icon: 'none'
});
}
} else {
uni.showToast({
title: '当前环境不支持文件选择',
icon: 'none'
});
}
};
// 预览/下载文件
const previewFile = (file) => {
if (!file.path) {
uni.showToast({
title: '文件路径不存在',
icon: 'none'
});
return;
}
const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
const ext = (file.name || file.path).split('.').pop()?.toLowerCase();
if (ext && imageExts.includes(ext)) {
uni.previewImage({
urls: [file.path],
current: file.path
});
} else {
// #ifdef H5
window.open(file.path, '_blank');
// #endif
// #ifdef APP-PLUS
plus.runtime.openURL(file.path);
// #endif
// #ifndef H5 || APP-PLUS
uni.showToast({
title: '点击下载文件',
icon: 'none'
});
uni.downloadFile({
url: file.path,
success: (res) => {
if (res.statusCode === 200) {
uni.openDocument({
filePath: res.tempFilePath,
success: () => {
console.log('打开文档成功');
},
fail: (err) => {
console.error('打开文档失败:', err);
uni.showToast({
title: '无法打开此文件',
icon: 'none'
});
}
});
}
},
fail: (err) => {
console.error('下载文件失败:', err);
uni.showToast({
title: '下载文件失败',
icon: 'none'
});
}
});
// #endif
}
};
// 删除文件
const removeFile = (index) => {
formData.value.attaches.splice(index, 1);
};
// 获取文件图标
const getFileIcon = (fileName) => {
const ext = fileName.split('.').pop()?.toLowerCase();
const iconMap = {
'pdf': '📄',
'doc': '📝',
'docx': '📝',
'xls': '📊',
'xlsx': '📊',
'ppt': '📊',
'pptx': '📊',
'zip': '📦',
'rar': '📦',
'7z': '📦',
'jpg': '🖼️',
'jpeg': '🖼️',
'png': '🖼️',
'gif': '🖼️',
'mp4': '🎬',
'avi': '🎬',
'mov': '🎬',
'mp3': '🎵',
'wav': '🎵'
};
return iconMap[ext] || '📎';
};
// 格式化文件大小
const formatFileSize = (bytes) => {
if (!bytes || bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
};
// 格式化日期时间(显示用)
const formatDateTime = (dateTime) => {
if (!dateTime) return '';
const date = new Date(dateTime);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 格式化日期时间(输入用)
const formatDateTimeForInput = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
2025-11-11 12:00:18 +08:00
// 使用统一映射函数获取状态文本
2025-11-10 14:14:49 +08:00
const getStatusText = (value) => {
2025-11-11 12:00:18 +08:00
return getCustomerStatusText(value, statusOptions.value);
2025-11-10 14:14:49 +08:00
};
2025-11-11 12:00:18 +08:00
// 使用统一映射函数获取意向强度文本
2025-11-10 14:14:49 +08:00
const getIntentLevelText = (value) => {
2025-11-11 12:00:18 +08:00
return getIntentLevelTextFromMapping(value, intentLevelOptions.value);
2025-11-10 14:14:49 +08:00
};
2025-11-11 12:00:18 +08:00
// 使用统一映射函数获取跟进方式文本
2025-11-10 14:14:49 +08:00
const getFollowTypeText = (value) => {
2025-11-11 12:00:18 +08:00
return getFollowTypeTextFromMapping(value, followTypeOptions.value);
2025-11-10 14:14:49 +08:00
};
// 提交表单
const handleSubmit = async () => {
if (!canSubmit.value) {
uni.showToast({
title: '请填写完整信息',
icon: 'none'
});
return;
}
submitting.value = true;
try {
const submitData = {
followId: formData.value.followId,
customerId: formData.value.customerId,
type: formData.value.type,
content: formData.value.content,
picture: formData.value.pictures.join(','),
attaches: formData.value.attaches.map(f => f.path).join(','),
followTime: formData.value.followTime,
nextFollowTime: formData.value.nextFollowTime,
customerStatus: formData.value.customerStatus,
customerIntentLevel: formData.value.customerIntentLevel
};
await updateFollowup(submitData);
uni.showToast({
title: '更新成功',
icon: 'success'
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} catch (err) {
console.error('更新跟进记录失败:', err);
uni.showToast({
title: err.message || '更新失败,请重试',
icon: 'none'
});
} finally {
submitting.value = false;
}
};
// 取消
const handleCancel = () => {
uni.showModal({
title: '提示',
content: '确定要取消吗?未保存的内容将丢失',
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
};
</script>
<style scoped lang="scss">
.edit-followup-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.custom-navbar {
background-color: #fff;
border-bottom: 1px solid #e0e0e0;
padding-top: var(--status-bar-height, 0);
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 16px;
}
.nav-btn {
font-size: 24px;
color: #333;
width: 40px;
text-align: center;
line-height: 44px;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
flex: 1;
text-align: center;
}
.loading-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.loading-text {
font-size: 14px;
color: #999;
}
.content-scroll {
flex: 1;
overflow-y: auto;
}
.form-container {
padding: 16px;
}
.form-section {
background-color: #fff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
}
.section-label {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.form-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.form-label {
font-size: 14px;
color: #333;
margin-bottom: 8px;
display: block;
&.required::before {
content: '*';
color: #f56c6c;
margin-right: 4px;
}
}
.form-input-wrapper {
display: flex;
align-items: center;
background-color: #f5f5f5;
border-radius: 8px;
padding: 12px;
&.readonly {
background-color: #f9f9f9;
.arrow {
display: none;
}
}
}
.form-input {
flex: 1;
font-size: 14px;
color: #333;
pointer-events: none;
}
.arrow {
font-size: 20px;
color: #999;
margin-left: 8px;
}
.textarea-wrapper {
position: relative;
background-color: #f5f5f5;
border-radius: 8px;
padding: 12px;
}
.form-textarea {
width: 100%;
min-height: 100px;
font-size: 14px;
color: #333;
line-height: 1.5;
}
.char-count {
position: absolute;
bottom: 8px;
right: 12px;
font-size: 12px;
color: #999;
}
.upload-area {
width: 33%;
aspect-ratio: 1;
border: 2px dashed #d0d0d0;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
background-color: #fafafa;
margin-bottom: 8px;
}
.upload-placeholder {
display: flex;
align-items: center;
justify-content: center;
}
.upload-icon {
font-size: 48px;
color: #999;
}
.upload-tips {
display: flex;
flex-direction: column;
gap: 4px;
text {
font-size: 12px;
color: #999;
line-height: 1.5;
}
}
.images-preview {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.image-item {
position: relative;
width: calc((100% - 16px) / 3);
aspect-ratio: 1;
border-radius: 4px;
overflow: hidden;
}
.preview-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.remove-btn {
width: 20px;
height: 20px;
background-color: rgba(0, 0, 0, 0.6);
color: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
line-height: 1;
}
.files-list {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 12px;
}
.file-item {
display: flex;
align-items: center;
padding: 12px;
background-color: #f5f5f5;
border-radius: 8px;
gap: 12px;
}
.file-icon {
font-size: 24px;
}
.file-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.file-name {
font-size: 14px;
color: #333;
}
.file-size {
font-size: 12px;
color: #999;
}
.button-wrapper {
padding: 16px;
background-color: #fff;
border-top: 1px solid #e0e0e0;
}
</style>