OfficeSystem/pages/customer/add/index.vue
2025-11-07 11:40:13 +08:00

1111 lines
29 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>
<view class="add-customer-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>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<view class="scroll-content">
<!-- 客户信息部分 -->
<view class="form-section">
<view class="section-title">客户信息</view>
<!-- 客户类型 -->
<view class="form-item">
<text class="form-label">客户类型</text>
<view class="radio-group">
<view
class="radio-item"
:class="{ active: formData.customerType === '个人' }"
@click="formData.customerType = '个人'"
>
<view class="radio-dot" :class="{ checked: formData.customerType === '个人' }"></view>
<text>个人</text>
</view>
<view
class="radio-item"
:class="{ active: formData.customerType === '企业' }"
@click="formData.customerType = '企业'"
>
<view class="radio-dot" :class="{ checked: formData.customerType === '企业' }"></view>
<text>企业</text>
</view>
</view>
</view>
<!-- 客户名称 -->
<view class="form-item">
<input
v-model="formData.name"
class="form-input"
placeholder="输入客户名称"
placeholder-style="color: #999;"
/>
</view>
<!-- 联系电话 -->
<view class="form-item">
<input
v-model="formData.mobile"
class="form-input"
placeholder="输入电话号码"
placeholder-style="color: #999;"
type="number"
/>
</view>
<!-- 微信号 -->
<view class="form-item">
<input
v-model="formData.wechat"
class="form-input"
placeholder="输入微信号"
placeholder-style="color: #999;"
/>
</view>
<!-- 客户来源 -->
<view class="form-item">
<input
v-model="formData.source"
class="form-input"
placeholder="输入客户来源"
placeholder-style="color: #999;"
/>
</view>
<!-- 客户意向 -->
<view class="form-item">
<input
v-model="formData.intents"
class="form-input"
placeholder="输入客户意向"
placeholder-style="color: #999;"
/>
</view>
<!-- 客户状态 -->
<view class="form-item clickable-item" @click="openStatusPicker">
<text v-if="formData.status" class="form-value">{{ getStatusText(formData.status) }}</text>
<text v-else class="form-placeholder">选择客户状态</text>
<text class="arrow"></text>
</view>
<!-- 意向强度 -->
<view class="form-item clickable-item" @click="openIntentLevelPicker">
<text v-if="formData.intentLevel" class="form-value">{{ getIntentLevelText(formData.intentLevel) }}</text>
<text v-else class="form-placeholder">选择意向强度</text>
<text class="arrow"></text>
</view>
<!-- 客户地区 -->
<view class="form-item clickable-item" @click="openRegionPicker" @tap="openRegionPicker">
<text v-if="formData.region" class="form-value">{{ formData.region }}</text>
<text v-else class="form-placeholder">选择客户地区</text>
<text class="arrow"></text>
</view>
<!-- 工作微信 -->
<view class="form-item clickable-item" @click="openWorkWechatPicker">
<text v-if="formData.workWechat" class="form-value">{{ formData.workWechat }}</text>
<text v-else class="form-placeholder">选择工作微信</text>
<text class="arrow"></text>
</view>
</view>
<!-- 其他信息部分 -->
<view class="form-section">
<view class="section-title">其他信息</view>
<!-- 客户星级 -->
<view class="form-item">
<text class="form-label">客户星级</text>
<view class="star-rating">
<text
class="star"
v-for="i in 5"
:key="i"
:class="{ 'filled': i <= formData.rating }"
@click="formData.rating = i"
>★</text>
</view>
</view>
<!-- 备注 -->
<view class="form-item">
<textarea
v-model="formData.remark"
class="form-textarea"
placeholder="输入备注"
placeholder-style="color: #999;"
auto-height
/>
</view>
<!-- 顾虑点 -->
<view class="form-item">
<textarea
v-model="formData.concern"
class="form-textarea"
placeholder="输入顾虑点"
placeholder-style="color: #999;"
auto-height
/>
</view>
<!-- 痛点 -->
<view class="form-item">
<textarea
v-model="formData.pain"
class="form-textarea"
placeholder="输入痛点"
placeholder-style="color: #999;"
auto-height
/>
</view>
<!-- 关注点 -->
<view class="form-item">
<textarea
v-model="formData.attention"
class="form-textarea"
placeholder="输入关注点"
placeholder-style="color: #999;"
auto-height
/>
</view>
<!-- 需求点 -->
<view class="form-item">
<textarea
v-model="formData.demand"
class="form-textarea"
placeholder="输入需求点"
placeholder-style="color: #999;"
auto-height
/>
</view>
<!-- 下次跟进时间 -->
<view class="form-item clickable-item" @click="openNextFollowTimePicker">
<text v-if="formData.nextFollowTime" class="form-value">{{ formData.nextFollowTime }}</text>
<text v-else class="form-placeholder">选择下次跟进时间(可选)</text>
<text class="arrow"></text>
</view>
</view>
</view>
</scroll-view>
<!-- 保存按钮 -->
<view class="save-button-wrapper">
<button class="save-button" @click="handleSave" :disabled="saving">保存</button>
</view>
<!-- 客户状态选择弹窗 -->
<view v-if="showStatusPicker" class="modal-mask" @click="showStatusPicker = false">
<view class="modal-content" @click.stop>
<view class="modal-title">选择客户状态</view>
<view class="picker-options">
<view
v-for="item in statusOptions"
:key="item.value"
class="picker-option"
:class="{ active: tempStatus === item.value }"
@click="selectStatus(item.value)"
>
<text>{{ item.label }}</text>
<text v-if="tempStatus === item.value" class="check">✓</text>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="showStatusPicker = false">取消</button>
<button class="modal-btn primary" @click="confirmStatus">确定</button>
</view>
</view>
</view>
<!-- 意向强度选择弹窗 -->
<view v-if="showIntentLevelPicker" class="modal-mask" @click="showIntentLevelPicker = false">
<view class="modal-content" @click.stop>
<view class="modal-title">选择意向强度</view>
<view class="picker-options">
<view
v-for="item in intentLevelOptions"
:key="item.value"
class="picker-option"
:class="{ active: tempIntentLevel === item.value }"
@click="selectIntentLevel(item.value)"
>
<text>{{ item.label }}</text>
<text v-if="tempIntentLevel === item.value" class="check">✓</text>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="showIntentLevelPicker = false">取消</button>
<button class="modal-btn primary" @click="confirmIntentLevel">确定</button>
</view>
</view>
</view>
<!-- 客户地区选择器 - uv-picker -->
<uv-picker
ref="regionPicker"
:columns="addressList"
:loading="regionLoading"
keyName="name"
@confirm="onRegionConfirm"
@change="onRegionChange"
></uv-picker>
<!-- 工作微信选择弹窗 -->
<view v-if="showWorkWechatPicker" class="modal-mask" @click="showWorkWechatPicker = false">
<view class="modal-content" @click.stop>
<view class="modal-title">选择工作微信</view>
<view class="picker-options">
<view
v-for="item in workWechatOptions"
:key="item.id"
class="picker-option"
:class="{ active: tempWorkWechat === item.value }"
@click="selectWorkWechat(item.value)"
>
<text>{{ item.label }}</text>
<text v-if="tempWorkWechat === item.value" class="check">✓</text>
</view>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="showWorkWechatPicker = false">取消</button>
<button class="modal-btn primary" @click="confirmWorkWechat">确定</button>
</view>
</view>
</view>
<!-- 下次跟进时间选择弹窗 -->
<view v-if="showNextFollowTimePicker" class="modal-mask" @click="showNextFollowTimePicker = false">
<view class="modal-content" @click.stop>
<view class="modal-title">选择下次跟进时间</view>
<view class="datetime-picker-wrapper" @click.stop>
<picker
mode="date"
:value="tempNextFollowDate"
@change="onNextFollowDateChange"
>
<view class="picker-display">日期: {{ tempNextFollowDate || '请选择日期' }}</view>
</picker>
<picker
mode="time"
:value="tempNextFollowTime"
@change="onNextFollowTimeChange"
>
<view class="picker-display">时间: {{ tempNextFollowTime || '请选择时间' }}</view>
</picker>
</view>
<view class="modal-buttons">
<button class="modal-btn" @click="clearNextFollowTime">清除</button>
<button class="modal-btn" @click="showNextFollowTimePicker = false">取消</button>
<button class="modal-btn primary" @click="confirmNextFollowTime">确定</button>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { createCustomer, getRegionTree } from '@/common/api';
import { useUserStore } from '@/store/user';
// 表单数据
const formData = ref({
customerType: '个人', // 客户类型:个人/企业
name: '', // 客户名称
mobile: '', // 联系电话
wechat: '', // 微信号
source: '', // 客户来源
intents: '', // 客户意向(字符串,提交时转换为数组)
status: '', // 客户状态
intentLevel: '', // 意向强度
region: '', // 客户地区(显示名称,如:北京/北京/朝阳)
regionIds: [], // 客户地区ID数组 [省ID, 市ID, 区ID]
workWechat: '', // 工作微信(显示名称)
workWechatId: null, // 工作微信ID
rating: 0, // 客户星级0-5
remark: '', // 备注
concern: '', // 顾虑点
pain: '', // 痛点
attention: '', // 关注点
demand: '', // 需求点
nextFollowTime: '' // 下次跟进时间
});
// 显示状态
const saving = ref(false);
const showStatusPicker = ref(false);
const showIntentLevelPicker = ref(false);
const showWorkWechatPicker = ref(false);
const showNextFollowTimePicker = ref(false);
// 临时选择值
const tempStatus = ref('');
const tempIntentLevel = ref('');
const tempWorkWechat = ref('');
const tempNextFollowDate = ref('');
const tempNextFollowTime = ref('');
// 地区树数据
const regionTree = ref([]);
// uv-picker 的列数据
const provinces = ref([]); // 省
const citys = ref([]); // 市
const areas = ref([]); // 区
const pickerValue = ref([0, 0, 0]); // 当前选中的索引
const regionLoading = ref(true); // 加载状态
// uv-picker 的引用
const regionPicker = ref(null);
// 计算属性:返回地址列表
const addressList = computed(() => {
return [provinces.value, citys.value, areas.value];
});
// 客户状态选项
const statusOptions = [
{ label: '潜在', value: '1' },
{ label: '意向', value: '2' },
{ label: '成交', value: '3' },
{ label: '失效', value: '4' }
];
// 意向强度选项
const intentLevelOptions = [
{ label: '高', value: '1' },
{ label: '中', value: '2' },
{ label: '低', value: '3' }
];
// 加载地区树数据
const loadRegionTree = async () => {
try {
const res = await getRegionTree();
console.log('获取的地区数据:', res);
regionTree.value = res;
// 初始化省份数据
provinces.value = res.sort((left, right) => (Number(left.code || left.id) > Number(right.code || right.id) ? 1 : -1));
// 如果有已选择的地区,设置默认值
if (formData.value.regionIds && formData.value.regionIds.length > 0) {
handlePickValueDefault();
} else {
// 初始化默认显示第一个省份的城市和区县
if (provinces.value.length > 0) {
citys.value = provinces.value[0]?.children || [];
if (citys.value.length > 0) {
areas.value = citys.value[0]?.children || [];
}
}
}
setTimeout(() => {
regionLoading.value = false;
}, 200);
} catch (err) {
console.error('加载地区树失败:', err);
regionLoading.value = false;
uni.showToast({
title: '加载地区数据失败',
icon: 'none'
});
}
};
// 根据已选择的ID设置默认选中项
const handlePickValueDefault = () => {
if (!formData.value.regionIds || formData.value.regionIds.length === 0) {
return;
}
const [provinceId, cityId, districtId] = formData.value.regionIds;
// 设置省
const provinceIndex = provinces.value.findIndex(item => Number(item.id) === Number(provinceId));
if (provinceIndex >= 0) {
pickerValue.value[0] = provinceIndex;
// 设置市
citys.value = provinces.value[provinceIndex]?.children || [];
const cityIndex = citys.value.findIndex(item => Number(item.id) === Number(cityId));
if (cityIndex >= 0) {
pickerValue.value[1] = cityIndex;
// 设置区
areas.value = citys.value[cityIndex]?.children || [];
const districtIndex = areas.value.findIndex(item => Number(item.id) === Number(districtId));
if (districtIndex >= 0) {
pickerValue.value[2] = districtIndex;
}
}
}
// 重置选择器位置
if (regionPicker.value) {
regionPicker.value.setIndexs([pickerValue.value[0], pickerValue.value[1], pickerValue.value[2]], true);
}
};
// 工作微信选项示例数据实际应从API获取包含ID和名称
const workWechatOptions = [
{ label: '工作微信1', value: '工作微信1', id: '1' },
{ label: '工作微信2', value: '工作微信2', id: '2' },
{ label: '工作微信3', value: '工作微信3', id: '3' },
{ label: '工作微信4', value: '工作微信4', id: '4' }
];
// 获取状态文本
const getStatusText = (status) => {
const option = statusOptions.find(opt => opt.value === status);
return option ? option.label : '';
};
// 获取意向强度文本
const getIntentLevelText = (level) => {
return level || '';
};
// 打开状态选择器
const openStatusPicker = () => {
tempStatus.value = formData.value.status;
showStatusPicker.value = true;
};
// 选择状态
const selectStatus = (value) => {
tempStatus.value = value;
};
// 确认状态
const confirmStatus = () => {
formData.value.status = tempStatus.value;
showStatusPicker.value = false;
};
// 打开意向强度选择器
const openIntentLevelPicker = () => {
tempIntentLevel.value = formData.value.intentLevel;
showIntentLevelPicker.value = true;
};
// 选择意向强度
const selectIntentLevel = (value) => {
tempIntentLevel.value = value;
};
// 确认意向强度
const confirmIntentLevel = () => {
formData.value.intentLevel = tempIntentLevel.value;
showIntentLevelPicker.value = false;
};
// 打开地区选择器
const openRegionPicker = () => {
console.log('openRegionPicker called');
// 检查数据是否已加载
if (regionLoading.value || !regionTree.value || regionTree.value.length === 0) {
uni.showToast({
title: '地区数据加载中,请稍候',
icon: 'none'
});
return;
}
// 如果有已选择的地区,恢复选择状态
if (formData.value.regionIds && formData.value.regionIds.length > 0) {
handlePickValueDefault();
} else {
// 如果没有已选择的地区,确保城市和区县数据已初始化
if (citys.value.length === 0 && provinces.value.length > 0) {
citys.value = provinces.value[0]?.children || [];
if (citys.value.length > 0) {
areas.value = citys.value[0]?.children || [];
}
}
}
// 打开选择器
if (regionPicker.value) {
regionPicker.value.open();
// 如果没有已选择的地区,在打开后设置默认索引
if (!formData.value.regionIds || formData.value.regionIds.length === 0) {
setTimeout(() => {
if (regionPicker.value) {
regionPicker.value.setIndexs([0, 0, 0], true);
}
}, 100);
}
}
};
// uv-picker 的 change 事件处理(实现三级联动)
const onRegionChange = (e) => {
if (regionLoading.value) return;
const { columnIndex, index, indexs } = e;
// 改变了省
if (columnIndex === 0) {
citys.value = provinces.value[index]?.children || [];
areas.value = citys.value[0]?.children || [];
if (regionPicker.value) {
regionPicker.value.setIndexs([index, 0, 0], true);
}
} else if (columnIndex === 1) {
// 改变了市
areas.value = citys.value[index]?.children || [];
if (regionPicker.value) {
regionPicker.value.setIndexs(indexs, true);
}
}
};
// uv-picker 的 confirm 事件处理
const onRegionConfirm = (e) => {
console.log('确认选择的地区:', e);
const { value } = e;
if (value && value.length > 0) {
const province = value[0];
const city = value[1];
const district = value[2];
// 构建地区名称和ID数组
const regionNames = [];
const regionIds = [];
if (province) {
regionNames.push(province.name);
regionIds.push(province.id);
if (city) {
regionNames.push(city.name);
regionIds.push(city.id);
if (district) {
regionNames.push(district.name);
regionIds.push(district.id);
}
}
}
formData.value.region = regionNames.join('/');
formData.value.regionIds = regionIds;
} else {
formData.value.region = '';
formData.value.regionIds = [];
}
};
// 打开工作微信选择器
const openWorkWechatPicker = () => {
tempWorkWechat.value = formData.value.workWechat;
showWorkWechatPicker.value = true;
};
// 选择工作微信
const selectWorkWechat = (value) => {
tempWorkWechat.value = value;
};
// 确认工作微信
const confirmWorkWechat = () => {
const selectedWechat = workWechatOptions.find(w => w.value === tempWorkWechat.value);
if (selectedWechat) {
formData.value.workWechat = selectedWechat.value;
formData.value.workWechatId = selectedWechat.id;
}
showWorkWechatPicker.value = false;
};
// 打开下次跟进时间选择器
const openNextFollowTimePicker = () => {
if (formData.value.nextFollowTime) {
const [date, time] = formData.value.nextFollowTime.split(' ');
tempNextFollowDate.value = date || '';
tempNextFollowTime.value = time || '';
} else {
tempNextFollowDate.value = '';
tempNextFollowTime.value = '';
}
showNextFollowTimePicker.value = true;
};
// 日期改变
const onNextFollowDateChange = (e) => {
tempNextFollowDate.value = e.detail.value;
};
// 时间改变
const onNextFollowTimeChange = (e) => {
tempNextFollowTime.value = e.detail.value;
};
// 清除下次跟进时间
const clearNextFollowTime = () => {
formData.value.nextFollowTime = '';
tempNextFollowDate.value = '';
tempNextFollowTime.value = '';
showNextFollowTimePicker.value = false;
};
// 确认下次跟进时间
const confirmNextFollowTime = () => {
if (tempNextFollowDate.value && tempNextFollowTime.value) {
formData.value.nextFollowTime = `${tempNextFollowDate.value} ${tempNextFollowTime.value}:00`;
} else if (tempNextFollowDate.value) {
formData.value.nextFollowTime = `${tempNextFollowDate.value} 09:00:00`;
} else {
formData.value.nextFollowTime = '';
}
showNextFollowTimePicker.value = false;
};
// 取消
const handleCancel = () => {
uni.navigateBack();
};
// 组件挂载时加载地区树数据
onMounted(() => {
loadRegionTree();
});
// 保存
const handleSave = async () => {
// 表单验证
if (!formData.value.name || formData.value.name.trim() === '') {
uni.showToast({
title: '请输入客户名称',
icon: 'none'
});
return;
}
if (!formData.value.mobile || formData.value.mobile.trim() === '') {
uni.showToast({
title: '请输入联系电话',
icon: 'none'
});
return;
}
saving.value = true;
try {
// 获取用户信息
const userStore = useUserStore();
const userId = userStore.userInfo?.id || userStore.userInfo?.userId || '1';
// 格式化当前时间
const now = new Date();
const formatDateTime = (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}`;
};
// 处理意向设备数组(如果输入的是逗号分隔的字符串,转换为数组)
let intentsArray = [];
if (formData.value.intents && formData.value.intents.trim()) {
intentsArray = formData.value.intents.split(',').map(item => item.trim()).filter(item => item);
}
// 处理地区ID数组使用已选择的regionIds
const regionIdsArray = formData.value.regionIds || [];
// 构建提交数据(按照接口示例格式)
const submitData = {
id: null,
code: null,
name: formData.value.name.trim(),
status: null,
intentLevel: null,
mobile: formData.value.mobile.trim(),
wechat: formData.value.wechat.trim() || null,
source: formData.value.source.trim() || null,
intents: intentsArray, // 数组格式
followId: userId, // 跟进人ID
remark: formData.value.remark.trim() || null,
type: '2', // 固定为2
workWechatId: formData.value.workWechatId || null,
regionIds: regionIdsArray, // 数组格式
follow: {
followTime: formatDateTime(now), // 当前时间作为跟进时间
nextFollowTime: formData.value.nextFollowTime || null,
customerStatus: formData.value.status || '1',
customerIntentLevel: formData.value.intentLevel || null
},
customerStatus: formData.value.status || '1',
nextFollowTime: formData.value.nextFollowTime || null,
customerIntentLevel: formData.value.intentLevel || null
};
// 调用API创建客户
await createCustomer(submitData);
uni.showToast({
title: '创建成功',
icon: 'success'
});
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack();
}, 1500);
} catch (error) {
console.error('创建客户失败:', error);
uni.showToast({
title: error.message || '创建失败,请重试',
icon: 'none'
});
} finally {
saving.value = false;
}
};
</script>
<style lang="scss" scoped>
.add-customer-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
/* 自定义导航栏 */
.custom-navbar {
background-color: #fff;
padding-top: var(--status-bar-height);
border-bottom: 1px solid #eee;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 16px;
}
.nav-btn {
font-size: 20px;
color: #333;
min-width: 44px;
text-align: center;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
/* 内容区域 */
.content-scroll {
flex: 1;
}
.scroll-content {
padding: 16px;
padding-bottom: 80px; /* 为保存按钮留出空间 */
}
/* 表单部分 */
.form-section {
background-color: #fff;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.form-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.form-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
display: block;
}
.form-input {
width: 100%;
height: 44px;
padding: 0 12px;
font-size: 15px;
color: #333;
background-color: #f8f8f8;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.form-textarea {
width: 100%;
min-height: 80px;
padding: 12px;
font-size: 15px;
color: #333;
background-color: #f8f8f8;
border-radius: 6px;
border: 1px solid #e0e0e0;
}
.clickable-item {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 12px;
background-color: #f8f8f8;
border-radius: 6px;
border: 1px solid #e0e0e0;
cursor: pointer;
pointer-events: auto;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
.form-value {
font-size: 15px;
color: #333;
}
.form-placeholder {
font-size: 15px;
color: #999;
}
.arrow {
font-size: 20px;
color: #999;
}
/* 单选框组 */
.radio-group {
display: flex;
gap: 24px;
}
.radio-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
color: #333;
cursor: pointer;
}
.radio-dot {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid #ddd;
background-color: #fff;
position: relative;
transition: all 0.3s;
&.checked {
border-color: #1976d2;
background-color: #1976d2;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 6px;
height: 6px;
border-radius: 50%;
background-color: #fff;
}
}
}
/* 星级评分 */
.star-rating {
display: flex;
gap: 8px;
align-items: center;
}
.star {
font-size: 24px;
color: #ddd;
cursor: pointer;
transition: color 0.2s;
&.filled {
color: #ffc107;
}
}
/* 保存按钮 */
.save-button-wrapper {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 12px 16px;
padding-bottom: calc(12px + env(safe-area-inset-bottom));
background-color: #fff;
border-top: 1px solid #eee;
z-index: 100;
}
.save-button {
width: 100%;
height: 44px;
background-color: #1976d2;
color: #fff;
font-size: 16px;
font-weight: 600;
border-radius: 6px;
border: none;
&:disabled {
background-color: #ccc;
opacity: 0.6;
}
}
/* 弹窗样式 */
.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%;
max-height: 70vh;
background-color: #fff;
border-radius: 16px 16px 0 0;
padding: 20px;
animation: slideUp 0.3s;
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: #333;
text-align: center;
margin-bottom: 20px;
}
.picker-options {
max-height: 400px;
overflow-y: auto;
}
.picker-option {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
font-size: 15px;
color: #333;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
&.active {
color: #1976d2;
}
}
.check {
color: #1976d2;
font-size: 18px;
}
.modal-buttons {
display: flex;
gap: 12px;
margin-top: 20px;
}
.datetime-picker-wrapper {
padding: 20px 0;
.picker-display {
padding: 12px;
margin-bottom: 12px;
background-color: #f8f8f8;
border-radius: 6px;
font-size: 15px;
color: #333;
text-align: center;
cursor: pointer;
pointer-events: auto;
user-select: none;
}
}
.modal-btn {
flex: 1;
height: 44px;
font-size: 15px;
border-radius: 6px;
border: 1px solid #e0e0e0;
background-color: #fff;
color: #666;
&.primary {
background-color: #1976d2;
color: #fff;
border-color: #1976d2;
}
}
</style>