2025-11-10 10:49:55 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="add-followup-page">
|
|
|
|
|
|
<!-- 自定义导航栏 -->
|
|
|
|
|
|
<view class="custom-navbar">
|
|
|
|
|
|
<view class="navbar-content">
|
|
|
|
|
|
<text class="nav-btn" @click="handleCancel">✕</text>
|
|
|
|
|
|
<text class="nav-title">{{ pageTitle }}</text>
|
|
|
|
|
|
<text class="nav-btn" style="opacity: 0;">占位</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 内容区域 -->
|
|
|
|
|
|
<scroll-view class="content-scroll" scroll-y>
|
|
|
|
|
|
<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>
|
2025-11-10 11:24:37 +08:00
|
|
|
|
|
2025-11-10 10:49:55 +08:00
|
|
|
|
</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">
|
|
|
|
|
|
<text class="form-label required">*客户</text>
|
2025-11-10 10:56:20 +08:00
|
|
|
|
<view class="form-input-wrapper readonly">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<input
|
|
|
|
|
|
v-model="formData.customerName"
|
|
|
|
|
|
class="form-input"
|
2025-11-10 10:56:20 +08:00
|
|
|
|
placeholder="客户信息"
|
2025-11-10 10:49:55 +08:00
|
|
|
|
disabled
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 客户状态 -->
|
2025-11-10 11:33:39 +08:00
|
|
|
|
<view class="form-item">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<text class="form-label required">*客户状态</text>
|
2025-11-10 11:33:39 +08:00
|
|
|
|
<view class="form-input-wrapper" @click="openStatusPicker">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<input
|
|
|
|
|
|
:value="getStatusText(formData.customerStatus)"
|
|
|
|
|
|
class="form-input"
|
|
|
|
|
|
placeholder="请选择客户状态"
|
|
|
|
|
|
disabled
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 意向强度 -->
|
2025-11-10 11:33:39 +08:00
|
|
|
|
<view class="form-item">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<text class="form-label required">*意向强度</text>
|
2025-11-10 11:33:39 +08:00
|
|
|
|
<view class="form-input-wrapper" @click="openIntentLevelPicker">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<input
|
|
|
|
|
|
:value="getIntentLevelText(formData.customerIntentLevel)"
|
|
|
|
|
|
class="form-input"
|
|
|
|
|
|
placeholder="请选择意向强度"
|
|
|
|
|
|
disabled
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 跟进方式 -->
|
2025-11-10 11:33:39 +08:00
|
|
|
|
<view class="form-item">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<text class="form-label required">*跟进方式</text>
|
2025-11-10 11:33:39 +08:00
|
|
|
|
<view class="form-input-wrapper" @click="openFollowTypePicker">
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<input
|
|
|
|
|
|
:value="getFollowTypeText(formData.type)"
|
|
|
|
|
|
class="form-input"
|
|
|
|
|
|
placeholder="请选择跟进方式"
|
|
|
|
|
|
disabled
|
|
|
|
|
|
placeholder-style="color: #999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<text class="arrow">›</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 跟进内容 -->
|
|
|
|
|
|
<view class="form-item">
|
|
|
|
|
|
<text class="form-label required">*跟进内容</text>
|
|
|
|
|
|
<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>
|
2025-11-10 11:24:37 +08:00
|
|
|
|
|
2025-11-10 10:49:55 +08:00
|
|
|
|
<!-- 文件列表 -->
|
|
|
|
|
|
<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) }}</text>
|
|
|
|
|
|
<view class="file-info">
|
|
|
|
|
|
<text class="file-name">{{ file.name }}</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">
|
|
|
|
|
|
<text class="form-label required">*跟进时间</text>
|
|
|
|
|
|
<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">
|
|
|
|
|
|
<text class="form-label required">*下次跟进</text>
|
|
|
|
|
|
<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"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 跟进时间选择器弹窗(移动端) -->
|
|
|
|
|
|
<view v-if="showFollowTimePicker" class="modal-mask" @click="showFollowTimePicker = false">
|
|
|
|
|
|
<view class="modal-content datetime-modal" @click.stop>
|
|
|
|
|
|
<view class="modal-title">选择跟进时间</view>
|
|
|
|
|
|
<picker-view
|
|
|
|
|
|
class="datetime-picker"
|
|
|
|
|
|
:value="followTimePickerValue"
|
|
|
|
|
|
@change="onFollowTimePickerChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(year, index) in dateTimePickerRange[0]" :key="index" class="picker-item-small">{{ year }}年</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(month, index) in dateTimePickerRange[1]" :key="index" class="picker-item-small">{{ month }}月</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(day, index) in dateTimePickerRange[2]" :key="index" class="picker-item-small">{{ day }}日</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(hour, index) in dateTimePickerRange[3]" :key="index" class="picker-item-small">{{ hour }}时</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(minute, index) in dateTimePickerRange[4]" :key="index" class="picker-item-small">{{ minute }}分</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
</picker-view>
|
|
|
|
|
|
<view class="modal-actions">
|
|
|
|
|
|
<text class="modal-btn cancel-btn" @click="showFollowTimePicker = false">取消</text>
|
|
|
|
|
|
<text class="modal-btn confirm-btn" @click="confirmFollowTime">确定</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 下次跟进时间选择器弹窗(移动端) -->
|
|
|
|
|
|
<view v-if="showNextFollowTimePicker" class="modal-mask" @click="showNextFollowTimePicker = false">
|
|
|
|
|
|
<view class="modal-content datetime-modal" @click.stop>
|
|
|
|
|
|
<view class="modal-title">选择下次跟进时间</view>
|
|
|
|
|
|
<picker-view
|
|
|
|
|
|
class="datetime-picker"
|
|
|
|
|
|
:value="nextFollowTimePickerValue"
|
|
|
|
|
|
@change="onNextFollowTimePickerChange"
|
|
|
|
|
|
>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(year, index) in dateTimePickerRange[0]" :key="index" class="picker-item-small">{{ year }}年</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(month, index) in dateTimePickerRange[1]" :key="index" class="picker-item-small">{{ month }}月</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(day, index) in dateTimePickerRange[2]" :key="index" class="picker-item-small">{{ day }}日</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(hour, index) in dateTimePickerRange[3]" :key="index" class="picker-item-small">{{ hour }}时</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
<picker-view-column>
|
|
|
|
|
|
<view v-for="(minute, index) in dateTimePickerRange[4]" :key="index" class="picker-item-small">{{ minute }}分</view>
|
|
|
|
|
|
</picker-view-column>
|
|
|
|
|
|
</picker-view>
|
|
|
|
|
|
<view class="modal-actions">
|
|
|
|
|
|
<text class="modal-btn cancel-btn" @click="showNextFollowTimePicker = false">取消</text>
|
|
|
|
|
|
<text class="modal-btn confirm-btn" @click="confirmNextFollowTime">确定</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 底部按钮 -->
|
|
|
|
|
|
<view class="button-wrapper">
|
|
|
|
|
|
<view class="button-group">
|
|
|
|
|
|
<button class="btn cancel-btn" @click="handleCancel">取消</button>
|
|
|
|
|
|
<button class="btn confirm-btn" @click="handleSubmit" :disabled="!canSubmit || submitting">
|
|
|
|
|
|
{{ submitting ? '提交中...' : '确定' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { ref, computed, onMounted } from 'vue';
|
|
|
|
|
|
import { onLoad } from '@dcloudio/uni-app';
|
|
|
|
|
|
import { chooseAndUploadImages, batchUploadFilesToQiniu } from '@/utils/qiniu.js';
|
|
|
|
|
|
import {
|
|
|
|
|
|
createFollowup,
|
|
|
|
|
|
getCustomerStatusDict,
|
|
|
|
|
|
getCustomerIntentLevelDict,
|
|
|
|
|
|
getCustomerFollowTypeDict
|
|
|
|
|
|
} from '@/common/api/customer.js';
|
|
|
|
|
|
import FollowupPickers from '@/components/followup-form/FollowupPickers.vue';
|
|
|
|
|
|
|
|
|
|
|
|
// 表单数据
|
|
|
|
|
|
const formData = ref({
|
|
|
|
|
|
customerId: '',
|
|
|
|
|
|
customerName: '',
|
|
|
|
|
|
customerStatus: '',
|
|
|
|
|
|
customerIntentLevel: '',
|
|
|
|
|
|
type: '',
|
|
|
|
|
|
content: '',
|
|
|
|
|
|
pictures: [],
|
|
|
|
|
|
attaches: [],
|
|
|
|
|
|
followTime: '',
|
|
|
|
|
|
nextFollowTime: ''
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 弹窗状态
|
|
|
|
|
|
const showStatusPicker = ref(false);
|
|
|
|
|
|
const showIntentLevelPicker = ref(false);
|
|
|
|
|
|
const showFollowTypePicker = ref(false);
|
|
|
|
|
|
const showFollowTimePicker = ref(false);
|
|
|
|
|
|
const showNextFollowTimePicker = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 选项数据
|
|
|
|
|
|
const statusOptions = ref([]);
|
|
|
|
|
|
const intentLevelOptions = ref([]);
|
|
|
|
|
|
const followTypeOptions = ref([]);
|
|
|
|
|
|
|
|
|
|
|
|
// 提交状态
|
|
|
|
|
|
const submitting = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 页面标题
|
|
|
|
|
|
const pageTitle = ref('新建跟进记录');
|
|
|
|
|
|
|
|
|
|
|
|
// 是否可以提交
|
|
|
|
|
|
const canSubmit = computed(() => {
|
|
|
|
|
|
return formData.value.customerId &&
|
|
|
|
|
|
formData.value.customerStatus &&
|
|
|
|
|
|
formData.value.customerIntentLevel &&
|
|
|
|
|
|
formData.value.type &&
|
|
|
|
|
|
formData.value.content.trim() &&
|
|
|
|
|
|
formData.value.followTime &&
|
|
|
|
|
|
formData.value.nextFollowTime;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 页面加载
|
|
|
|
|
|
onLoad((options) => {
|
|
|
|
|
|
// 判断是新建还是修改
|
|
|
|
|
|
if (options.id) {
|
|
|
|
|
|
// 如果有ID,说明是修改模式
|
|
|
|
|
|
pageTitle.value = '修改跟进记录';
|
|
|
|
|
|
// TODO: 加载跟进记录详情
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 新建模式
|
|
|
|
|
|
pageTitle.value = '新建跟进记录';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果传入了客户ID,预填充客户信息
|
|
|
|
|
|
if (options.customerId) {
|
|
|
|
|
|
formData.value.customerId = options.customerId;
|
|
|
|
|
|
formData.value.customerName = decodeURIComponent(options.customerName || '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载字典数据
|
|
|
|
|
|
loadDictData();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置默认跟进时间为当前时间
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
formData.value.followTime = formatDateTimeForInput(now);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置默认下次跟进时间为7天后
|
|
|
|
|
|
const nextWeek = new Date(now);
|
|
|
|
|
|
nextWeek.setDate(nextWeek.getDate() + 7);
|
|
|
|
|
|
formData.value.nextFollowTime = formatDateTimeForInput(nextWeek);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 加载字典数据
|
|
|
|
|
|
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 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 generateDateTimePickerRange = () => {
|
|
|
|
|
|
const years = [];
|
|
|
|
|
|
const months = [];
|
|
|
|
|
|
const days = [];
|
|
|
|
|
|
const hours = [];
|
|
|
|
|
|
const minutes = [];
|
|
|
|
|
|
|
|
|
|
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
|
|
for (let i = currentYear - 1; i <= currentYear + 1; i++) {
|
|
|
|
|
|
years.push(i.toString());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= 12; i++) {
|
|
|
|
|
|
months.push(String(i).padStart(2, '0'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i <= 31; i++) {
|
|
|
|
|
|
days.push(String(i).padStart(2, '0'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 24; i++) {
|
|
|
|
|
|
hours.push(String(i).padStart(2, '0'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 60; i += 1) {
|
|
|
|
|
|
minutes.push(String(i).padStart(2, '0'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [years, months, days, hours, minutes];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 日期时间选择器范围数据(需要在模板中使用)
|
|
|
|
|
|
const dateTimePickerRange = generateDateTimePickerRange();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前日期时间在picker中的索引
|
|
|
|
|
|
const getDateTimePickerValue = (dateTime) => {
|
|
|
|
|
|
if (!dateTime) {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
return [
|
|
|
|
|
|
dateTimePickerRange[0].indexOf(now.getFullYear().toString()),
|
|
|
|
|
|
now.getMonth(),
|
|
|
|
|
|
now.getDate() - 1,
|
|
|
|
|
|
now.getHours(),
|
|
|
|
|
|
now.getMinutes()
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const d = new Date(dateTime);
|
|
|
|
|
|
return [
|
|
|
|
|
|
dateTimePickerRange[0].indexOf(d.getFullYear().toString()),
|
|
|
|
|
|
d.getMonth(),
|
|
|
|
|
|
d.getDate() - 1,
|
|
|
|
|
|
d.getHours(),
|
|
|
|
|
|
d.getMinutes()
|
|
|
|
|
|
];
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const followTimePickerValue = ref(getDateTimePickerValue(formData.value.followTime));
|
|
|
|
|
|
const nextFollowTimePickerValue = ref(getDateTimePickerValue(formData.value.nextFollowTime));
|
|
|
|
|
|
|
|
|
|
|
|
// 跟进时间选择器变化(picker-view)
|
|
|
|
|
|
const onFollowTimePickerChange = (e) => {
|
|
|
|
|
|
followTimePickerValue.value = e.detail.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认跟进时间
|
|
|
|
|
|
const confirmFollowTime = () => {
|
|
|
|
|
|
const [yearIdx, monthIdx, dayIdx, hourIdx, minuteIdx] = followTimePickerValue.value;
|
|
|
|
|
|
const year = dateTimePickerRange[0][yearIdx];
|
|
|
|
|
|
const month = dateTimePickerRange[1][monthIdx];
|
|
|
|
|
|
const day = dateTimePickerRange[2][dayIdx];
|
|
|
|
|
|
const hour = dateTimePickerRange[3][hourIdx];
|
|
|
|
|
|
const minute = dateTimePickerRange[4][minuteIdx];
|
|
|
|
|
|
|
|
|
|
|
|
formData.value.followTime = `${year}-${month}-${day} ${hour}:${minute}:00`;
|
|
|
|
|
|
showFollowTimePicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 下次跟进时间选择器变化(picker-view)
|
|
|
|
|
|
const onNextFollowTimePickerChange = (e) => {
|
|
|
|
|
|
nextFollowTimePickerValue.value = e.detail.value;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 确认下次跟进时间
|
|
|
|
|
|
const confirmNextFollowTime = () => {
|
|
|
|
|
|
const [yearIdx, monthIdx, dayIdx, hourIdx, minuteIdx] = nextFollowTimePickerValue.value;
|
|
|
|
|
|
const year = dateTimePickerRange[0][yearIdx];
|
|
|
|
|
|
const month = dateTimePickerRange[1][monthIdx];
|
|
|
|
|
|
const day = dateTimePickerRange[2][dayIdx];
|
|
|
|
|
|
const hour = dateTimePickerRange[3][hourIdx];
|
|
|
|
|
|
const minute = dateTimePickerRange[4][minuteIdx];
|
|
|
|
|
|
|
|
|
|
|
|
formData.value.nextFollowTime = `${year}-${month}-${day} ${hour}:${minute}:00`;
|
|
|
|
|
|
showNextFollowTimePicker.value = false;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开跟进时间选择器
|
|
|
|
|
|
const openFollowTimePicker = () => {
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
// H5平台使用input type="datetime-local"
|
|
|
|
|
|
const currentDate = formData.value.followTime || new Date();
|
|
|
|
|
|
const d = currentDate instanceof Date ? currentDate : new Date(currentDate);
|
|
|
|
|
|
const year = d.getFullYear();
|
|
|
|
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
|
|
|
|
const hours = String(d.getHours()).padStart(2, '0');
|
|
|
|
|
|
const minutes = String(d.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
const dateStr = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
|
|
|
|
|
|
|
|
|
|
const input = document.createElement('input');
|
|
|
|
|
|
input.type = 'datetime-local';
|
|
|
|
|
|
input.value = dateStr;
|
|
|
|
|
|
input.onchange = (e) => {
|
|
|
|
|
|
if (e.target.value) {
|
|
|
|
|
|
formData.value.followTime = e.target.value.replace('T', ' ') + ':00';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
input.click();
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
// 其他平台使用picker-view组件
|
|
|
|
|
|
followTimePickerValue.value = getDateTimePickerValue(formData.value.followTime);
|
|
|
|
|
|
showFollowTimePicker.value = true;
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开下次跟进时间选择器
|
|
|
|
|
|
const openNextFollowTimePicker = () => {
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
// H5平台使用input type="datetime-local"
|
|
|
|
|
|
const currentDate = formData.value.nextFollowTime || new Date();
|
|
|
|
|
|
const d = currentDate instanceof Date ? currentDate : new Date(currentDate);
|
|
|
|
|
|
const year = d.getFullYear();
|
|
|
|
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
|
|
|
|
const hours = String(d.getHours()).padStart(2, '0');
|
|
|
|
|
|
const minutes = String(d.getMinutes()).padStart(2, '0');
|
|
|
|
|
|
const dateStr = `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
|
|
|
|
|
|
|
|
|
|
const input = document.createElement('input');
|
|
|
|
|
|
input.type = 'datetime-local';
|
|
|
|
|
|
input.value = dateStr;
|
|
|
|
|
|
input.onchange = (e) => {
|
|
|
|
|
|
if (e.target.value) {
|
|
|
|
|
|
formData.value.nextFollowTime = e.target.value.replace('T', ' ') + ':00';
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
input.click();
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// #ifndef H5
|
|
|
|
|
|
// 其他平台使用picker-view组件
|
|
|
|
|
|
nextFollowTimePickerValue.value = getDateTimePickerValue(formData.value.nextFollowTime);
|
|
|
|
|
|
showNextFollowTimePicker.value = true;
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 选择图片并自动上传
|
|
|
|
|
|
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.chooseFile(H5和部分平台支持)
|
|
|
|
|
|
// #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, // 保存七牛云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);
|
|
|
|
|
|
// 如果uni.chooseFile不支持,尝试使用原生方法
|
|
|
|
|
|
chooseFilesNative();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 如果不支持uni.chooseFile,使用原生方法
|
|
|
|
|
|
chooseFilesNative();
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// #ifndef H5 || MP-WEIXIN || APP-PLUS
|
|
|
|
|
|
// 其他平台使用原生方法
|
|
|
|
|
|
chooseFilesNative();
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 原生文件选择方法(安卓平台)
|
|
|
|
|
|
const chooseFilesNative = async () => {
|
|
|
|
|
|
const remainingCount = 10 - formData.value.attaches.length;
|
|
|
|
|
|
|
|
|
|
|
|
// 安卓平台使用 plus API 调用原生文件选择器
|
|
|
|
|
|
if (typeof plus !== 'undefined') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const Intent = plus.android.importClass('android.content.Intent');
|
|
|
|
|
|
const main = plus.android.runtimeMainActivity();
|
|
|
|
|
|
|
|
|
|
|
|
// 创建文件选择 Intent
|
|
|
|
|
|
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) { // RESULT_OK = -1
|
|
|
|
|
|
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, // 保存 URI 字符串
|
|
|
|
|
|
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, // 保存 URI 字符串
|
|
|
|
|
|
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, // 保存七牛云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'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 恢复原始的 onActivityResult
|
|
|
|
|
|
if (originalOnActivityResult) {
|
|
|
|
|
|
main.onActivityResult = originalOnActivityResult;
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
console.error('处理文件选择结果失败:', error);
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '处理文件失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 调用原始的 onActivityResult
|
|
|
|
|
|
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.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'
|
|
|
|
|
|
});
|
|
|
|
|
|
// 可以调用下载API
|
|
|
|
|
|
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}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取状态文本
|
|
|
|
|
|
const getStatusText = (value) => {
|
|
|
|
|
|
const item = statusOptions.value.find(item => item.dictValue === value);
|
|
|
|
|
|
return item ? item.dictLabel : '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取意向强度文本
|
|
|
|
|
|
const getIntentLevelText = (value) => {
|
|
|
|
|
|
const item = intentLevelOptions.value.find(item => item.dictValue === value);
|
|
|
|
|
|
return item ? item.dictLabel : '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取跟进方式文本
|
|
|
|
|
|
const getFollowTypeText = (value) => {
|
|
|
|
|
|
const item = followTypeOptions.value.find(item => item.dictValue === value);
|
|
|
|
|
|
return item ? item.dictLabel : '';
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 提交表单
|
|
|
|
|
|
const handleSubmit = async () => {
|
|
|
|
|
|
if (!canSubmit.value) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '请填写完整信息',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
submitting.value = true;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const submitData = {
|
|
|
|
|
|
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 createFollowup(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">
|
|
|
|
|
|
.add-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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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;
|
2025-11-10 10:56:20 +08:00
|
|
|
|
|
|
|
|
|
|
&.readonly {
|
|
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
|
|
|
|
|
|
|
|
.arrow {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-10 10:49:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.form-input {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #333;
|
2025-11-10 11:33:39 +08:00
|
|
|
|
pointer-events: none; /* 让点击事件穿透到父元素 */
|
2025-11-10 10:49:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.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 {
|
2025-11-10 10:56:20 +08:00
|
|
|
|
width: 33%;
|
2025-11-10 10:49:55 +08:00
|
|
|
|
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 {
|
2025-11-10 11:46:51 +08:00
|
|
|
|
|
2025-11-10 10:49:55 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-mask {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-end;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-content {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 16px 16px 0 0;
|
|
|
|
|
|
max-height: 70vh;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-modal {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border-bottom: 1px solid #e0e0e0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-scroll {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
max-height: 400px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
color: #1976d2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.check-icon {
|
|
|
|
|
|
color: #1976d2;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
border-top: 1px solid #e0e0e0;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.modal-btn {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
|
|
|
|
|
|
&.cancel-btn {
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.confirm-btn {
|
|
|
|
|
|
background-color: #1976d2;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.datetime-modal {
|
|
|
|
|
|
max-height: 60vh;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.datetime-picker {
|
|
|
|
|
|
height: 200px;
|
|
|
|
|
|
margin: 16px 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.picker-item-small {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.button-wrapper {
|
|
|
|
|
|
padding: 16px;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-top: 1px solid #e0e0e0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.button-group {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.btn {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
|
|
|
|
|
|
&.cancel-btn {
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.confirm-btn {
|
|
|
|
|
|
background-color: #1976d2;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
background-color: #ccc;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|