alipay-emulator/components/call-log/list/list.vue

1896 lines
44 KiB
Vue
Raw Permalink 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="call-list" :class="['call-list-' + type]">
<view v-for="(item, index) in list" :key="index" class="item" @touchstart="touchStart($event, index)"
@touchmove="touchMove($event, index)" @touchend="touchEnd($event, index)"
:style="{ 'transform': 'translateX(' + swiperList[index] + 'px)', 'transition': swiperList[index] == 0 ? 'all 0.3s' : '' }">
<view class="avatar" v-if="type != 'xiaomi'">
<!-- 显示文字头像 -->
<view v-if="type == 'ios' && item.name && item.avatarType === 'text'" class="text-avatar" :class="{'text-avatar-18':getAvatarLength(item.name)==2}">
{{ getAvatarText(item.name) }}
</view>
<!-- 显示图片头像 -->
<image class="iosAvatar" v-else-if="type == 'ios' && item.avatar" :src="item.avatar" mode=""></image>
<!-- 默认头像 -->
<image v-else-if="type == 'ios'" src="/static/image/call/iosAvatar.png" mode=""></image>
<!-- 其他类型的状态图标 -->
<image v-else-if="type == 'oppo'&&item.status==3" :src="`/static/image/call/oppoStatusIcon1.png`"
mode="widthFix"> </image>
<image v-else-if="type == 'oppo'&&(item.status==0||item.status==1)"
:src="`/static/image/call/oppoStatusIcon2.png`" mode="widthFix"> </image>
<image v-else-if="type == 'oppo'&&(item.status==4||item.status==5)"
:src="`/static/image/call/oppoStatusIcon3.png`" mode="widthFix"> </image>
<image v-else-if="type == 'oppo'&&item.status==2" :src="`/static/image/call/oppoStatusIcon4.png`"
mode="widthFix"> </image>
<image v-else-if="type == 'huawei'&&item.status==3" :src="`/static/image/call/huaweiStatusIcon2.png`"
mode="widthFix"></image>
<image v-else-if="type == 'huawei'&&item.status<3" :src="`/static/image/call/huaweiStatusIcon1.png`"
mode="widthFix"></image>
<image v-else-if="type == 'vivo'&&item.status==3" :src="`/static/image/call/vivoStatusIcon2.png`"
mode="widthFix"> </image>
<image v-else-if="type == 'vivo'&&(item.status==1||item.status==2)"
:src="`/static/image/call/vivoStatusIcon1.png`" mode="widthFix"> </image>
</view>
<view class="infoBox">
<view class="left-box">
<view class="leftInfo">
<view class="title" :class="{ 'title-red': item.status == 3 }">
<!-- ios -->
<view class="notes" v-if="type == 'ios'">
{{ item.name || formatString(item.phone) }}
</view>
<!-- xiaomi -->
<view class="notes" v-else-if="type == 'xiaomi'">
{{ item.name || item.notes || formatString(item.phone) }}
<text v-if="!item.name && item.notes">{{ formatString(item.phone) }}</text>
</view>
<view class="notes" v-else-if="type == 'oppo'">
{{ item.name || formatString(item.phone) }}
</view>
<view class="notes" v-else-if="type == 'huawei'">
{{ item.name || formatString(item.phone) }}
</view>
<view class="notes" v-else-if="type == 'vivo'">
{{ item.name || formatString(item.phone) }}
</view>
</view>
<view class="info">
<!-- 电话 -->
<view class="phone" v-if="type == 'vivo'">
{{ item.notes || formatString(item.phone) }}
</view>
<!-- 卡几 -->
<view class="kj" v-if="type == 'oppo' || type == 'huawei'">
<image :src="`/static/image/call/${type}KJ${item.kj}.png`" mode=""></image>
</view>
<!-- 时间 -->
<view class="time" v-if="type == 'xiaomi'">
{{ formatTime(item.time) }}
</view>
<!-- icon -->
<view class="status-icon" v-if="type == 'ios' && Number(item.status) > 2">
<image src="/static/image/call/iosStatusIcon.png" mode=""></image>
</view>
<!-- 地址 -->
<view class="address" v-if="type != 'ios'">
{{ item.address }}
</view>
<!-- 运营商 -->
<view class="yys" v-if="!(type == 'huawei'&&item.notes)">
{{ item.yys }}
</view>
<!-- 电话 -->
<view class="phone" v-if="type == 'ios' && item.name">
{{ formatString(item.phone) }}
</view>
<!-- 备注 -->
<view class="notes" v-if="type == 'oppo' || type == 'huawei'">
{{ item.notes }}
</view>
</view>
</view>
</view>
<view class="right-box">
<!-- 时间 -->
<view class="time" v-if="type != 'xiaomi'">
{{ formatTime(item.time) }}
</view>
<!-- 图标 -->
<view class="icon">
<image :src="`/static/image/call/${type}RightIcon.png`" mode=""></image>
</view>
</view>
</view>
<!-- 编辑按钮 -->
<view class="edit-btn" @click="editItem(index)">
<text class="edit-text">编辑</text>
</view>
<!-- 删除按钮 -->
<view class="delete-btn" @click="deleteItem(index)">
<text class="delete-text">删除</text>
</view>
</view>
</view>
<!-- 编辑弹窗 -->
<view class="modal-mask" v-if="showEditModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">编辑通话记录</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">姓名</text>
<input class="form-input" v-model="editForm.name" placeholder="请输入姓名" />
</view>
<view class="form-item">
<text class="form-label">电话</text>
<input class="form-input" v-model="editForm.phone" placeholder="请输入电话" />
</view>
<view class="form-item">
<text class="form-label">备注</text>
<input class="form-input" v-model="editForm.notes" placeholder="请输入备注(骚扰电话)" />
</view>
<view class="form-item">
<text class="form-label">运营商</text>
<uni-data-select class="form-select" v-model="editForm.yys" :localdata="yysOptions" @change="onYysChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">地址</text>
<input class="form-input" v-model="editForm.address" placeholder="请输入地址" />
</view>
<view class="form-item">
<text class="form-label">时间</text>
<view class="form-input form-input-time" @click="setTimeType">
{{ editForm.time }}
</view>
</view>
<view class="form-item">
<text class="form-label">卡几</text>
<uni-data-select class="form-select" v-model="editForm.kj" :localdata="kjOptions" @change="onKjChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">状态</text>
<uni-data-select class="form-select" v-model="editForm.status" :localdata="statusOptions" @change="onStatusChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">电话备注</text>
<input class="form-input" v-model="editForm.phoneNotes" placeholder="请输入电话备注" />
</view>
<view class="form-item">
<text class="form-label">头像类型</text>
<uni-data-select class="form-select" v-model="editForm.avatarType" :localdata="avatarTypeOptions" @change="onAvatarTypeChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">头像</text>
<view class="form-avatar-container">
<view v-if="editForm.avatarType === 'image'" class="form-avatar" @click="choose()">
<image :src="editForm.avatar||'/static/image/call/iosAvatar.png'" mode=""></image>
</view>
<view v-else-if="editForm.avatarType === 'text'" class="form-text-avatar">
{{ editForm.name ? getAvatarText(editForm.name) : '请输入姓名' }}
</view>
<view v-if="editForm.avatar" class="avatar-clear" @click="clearAvatar">清除</view>
</view>
</view>
</view>
<view class="modal-footer">
<view class="btn btn-cancel" @click="closeEditModal">取消</view>
<view class="btn btn-confirm" @click="saveEdit">保存</view>
</view>
</view>
<uni-popup ref="timepopup" type="bottom">
<view class="timeBox">
<view class="titleBox">
<view class="title">
</view>
<view class="btns" @click="settmes">
确定
</view>
</view>
<DateTimePicker :defaultDate="data.startDate" :maxDate="data.endDate" :mode="4" @onChange="onChangeStartDate" />
</view>
</uni-popup>
</view>
<!-- 添加联系人弹窗 -->
<view class="modal-mask" v-if="showAddModal">
<view class="modal-content" @click.stop>
<view class="modal-header">
<text class="modal-title">添加联系人</text>
</view>
<view class="modal-body">
<view class="form-item">
<text class="form-label">姓名</text>
<input class="form-input" v-model="addForm.name" placeholder="请输入姓名" />
</view>
<view class="form-item">
<text class="form-label">电话 <text>*</text></text>
<input class="form-input" v-model="addForm.phone" placeholder="请输入电话" />
</view>
<view class="form-item">
<text class="form-label">运营商 <text>*</text></text>
<uni-data-select class="form-select" v-model="addForm.yys" :localdata="yysOptions" @change="onAddYysChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">时间 <text>*</text></text>
<view class="form-input form-input-time" @click="setAddTimeType">
{{ addForm.time }}
</view>
</view>
<view class="form-item">
<text class="form-label">状态 <text>*</text></text>
<uni-data-select class="form-select" v-model="addForm.status" :localdata="statusOptions" @change="onAddStatusChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">卡几</text>
<uni-data-select class="form-select" v-model="addForm.kj" :localdata="kjOptions" @change="onAddKjChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">地址</text>
<input class="form-input" v-model="addForm.address" placeholder="请输入地址" />
</view>
<view class="form-item">
<text class="form-label">备注</text>
<input class="form-input" v-model="addForm.notes" placeholder="请输入备注" />
</view>
<view class="form-item">
<text class="form-label">电话备注</text>
<input class="form-input" v-model="addForm.phoneNotes" placeholder="请输入电话备注" />
</view>
<view class="form-item">
<text class="form-label">头像类型</text>
<uni-data-select class="form-select" v-model="addForm.avatarType" :localdata="avatarTypeOptions" @change="onAddAvatarTypeChange"
:clear="false"></uni-data-select>
</view>
<view class="form-item">
<text class="form-label">头像</text>
<view class="form-avatar-container">
<view v-if="addForm.avatarType === 'image'" class="form-avatar" @click="choose()">
<image :src="addForm.avatar||'/static/image/call/iosAvatar.png'" mode=""></image>
</view>
<view v-else-if="addForm.avatarType === 'text'" class="form-text-avatar">
{{ addForm.name ? getAvatarText(addForm.name) : '请输入姓名' }}
</view>
<view v-if="addForm.avatar" class="avatar-clear" @click="clearAvatar">清除</view>
</view>
</view>
<view class="form-item">
<text class="form-label">随机数据</text>
<image class="suiji" src="/static/image/common/random-dice.png" mode="" @click="generateRandomData"></image>
</view>
</view>
<view class="modal-footer">
<!-- <view class="btn btn-random" @click="generateRandomData">随机数据</view> -->
<view class="btn btn-cancel" @click="closeAddModal">取消</view>
<view class="btn btn-confirm" @click="saveAdd">保存</view>
</view>
</view>
<uni-popup ref="timepopup" type="bottom">
<view class="timeBox">
<view class="titleBox">
<view class="title">
</view>
<view class="btns" @click="settmes">
确定
</view>
</view>
<DateTimePicker :defaultDate="data.startDate" :maxDate="data.endDate" :mode="4" @onChange="onChangeStartDate" />
</view>
</uni-popup>
</view>
</template>
<script setup>
import {
ref,
reactive,
onMounted,
watch,
computed
} from 'vue';
import {
onUnload
} from "@dcloudio/uni-app";
import DateTimePicker from '@/components/dengrq-datetime-picker/dateTimePicker/index.vue';
const props = defineProps({
isHuise: {
type: Boolean,
default: true
},
type: {
type: String,
default: 'ios'
}
});
const data = reactive({
startDate: '',
timeIndex: -1
})
const emit = defineEmits(['update:list']);
const list = ref([{
"avatar": "",
"name": "李强",
"phone": "13912345678",
"phoneNotes": "电话",
"yys": "移动",
"kj": "2",
"address": "北京",
"time": "2026-02-01 10:00:00",
"status": 1,
"avatarType": 'image', // image 或 text
"notes": "工作往来"
},
{
"avatar": "",
"name": "王芳",
"phone": "15887654321",
"phoneNotes": "电话",
"yys": "联通",
"kj": "2",
"address": "上海",
"time": "2026-02-02 14:30:00",
"status": 4,
"avatarType": 'image', // image 或 text
"notes": "老朋友"
},
{
"avatar": "",
"name": "",
"phone": "18711223344",
"phoneNotes": "电话",
"yys": "移动",
"kj": "1",
"address": "广州",
"time": "2026-03-03 16:45:00",
"status": 0,
"avatarType": 'image', // image 或 text
"notes": "推销电话"
},
{
"avatar": "",
"name": "",
"phone": "13644556677",
"phoneNotes": "电话",
"yys": "电信",
"kj": "2",
"address": "深圳",
"time": "2026-03-04 10:30:00",
"status": 2,
"avatarType": 'image', // image 或 text
"notes": "快递小哥"
},
{
"avatar": "",
"name": "陈静",
"phone": "17788889999",
"phoneNotes": "电话",
"yys": "联通",
"kj": "2",
"address": "杭州",
"time": "2026-03-05 15:00:00",
"status": 3,
"avatarType": 'image', // image 或 text
"notes": "大学同学"
},
{
"avatar": "",
"name": "赵雷",
"phone": "15133334444",
"phoneNotes": "电话",
"yys": "移动",
"kj": "1",
"address": "成都",
"time": "2026-03-01 18:00:00",
"status": 5,
"avatarType": 'image', // image 或 text
"notes": "骚扰电话"
},
{
"avatar": "",
"name": "孙丽",
"phone": "18966667777",
"phoneNotes": "电话",
"yys": "电信",
"kj": "2",
"address": "武汉",
"time": "2026-02-05 18:30:00",
"status": 1,
"avatarType": 'image', // image 或 text
"notes": "客户"
},
{
"avatar": "",
"name": "周涛",
"phone": "15555558888",
"phoneNotes": "电话",
"yys": "联通",
"kj": "1",
"address": "西安",
"time": "2026-02-05 18:30:00",
"status": 4,
"avatarType": 'image', // image 或 text
"notes": "家人"
},
{
"avatar": "",
"name": "吴杰",
"phone": "13011112222",
"phoneNotes": "电话",
"yys": "电信",
"kj": "2",
"address": "南京",
"time": "2026-02-05 18:30:00",
"status": 0,
"avatarType": 'image', // image 或 text
"notes": "广告推销"
},
{
"avatar": "",
"name": "郑爽",
"phone": "18899990000",
"phoneNotes": "电话",
"yys": "移动",
"kj": "2",
"address": "长沙",
"time": "2026-03-05 18:30:00",
"status": 2,
"avatarType": 'image', // image 或 text
"notes": "同事"
},
{
"avatar": "",
"name": "",
"phone": "15712345678",
"phoneNotes": "电话",
"yys": "移动",
"kj": "2",
"address": "天津",
"time": "2025-08-05 18:30:00",
"status": 3,
"avatarType": 'image', // image 或 text
"notes": "未知号码"
},
{
"avatar": "",
"name": "李娜",
"phone": "15287654321",
"phoneNotes": "电话",
"yys": "联通",
"kj": "1",
"address": "苏州",
"time": "2026-03-05 18:30:00",
"status": 5,
"avatarType": 'image', // image 或 text
"notes": "健身房"
},
{
"avatar": "",
"name": "张敏",
"phone": "18799887766",
"phoneNotes": "电话",
"yys": "移动",
"kj": "1",
"address": "青岛",
"time": "2026-03-05 18:30:00",
"status": 1,
"avatarType": 'image', // image 或 text
"notes": "学校老师"
},
{
"avatar": "",
"name": "",
"phone": "13655443322",
"phoneNotes": "电话",
"yys": "电信",
"kj": "2",
"address": "大连",
"time": "2026-03-05 18:30:00",
"status": 4,
"avatarType": 'image', // image 或 text
"notes": "保险推销"
},
{
"avatar": "",
"name": "",
"phone": "17722334455",
"phoneNotes": "电话",
"yys": "联通",
"kj": "2",
"address": "厦门",
"time": "2026-03-05 18:30:00",
"status": 0,
"avatarType": 'image', // image 或 text
"notes": "外卖"
},
{
"avatar": "",
"name": "赵雅",
"phone": "15166778899",
"phoneNotes": "电话",
"yys": "移动",
"kj": "2",
"address": "宁波",
"time": "2026-03-05 18:30:00",
"status": 2,
"avatarType": 'image', // image 或 text
"notes": "亲戚"
},
{
"avatar": "",
"name": "孙浩",
"phone": "18911223344",
"phoneNotes": "电话",
"yys": "电信",
"kj": "1",
"address": "郑州",
"time": "2026-03-05 18:30:00",
"status": 3,
"avatarType": 'image', // image 或 text
"notes": "房产中介"
},
{
"avatar": "",
"name": "",
"phone": "15544332211",
"phoneNotes": "电话",
"yys": "联通",
"kj": "1",
"address": "沈阳",
"time": "2026-03-05 18:30:00",
"status": 5,
"avatarType": 'image', // image 或 text
"notes": "银行客服"
},
{
"avatar": "",
"name": "吴刚",
"phone": "13099887766",
"phoneNotes": "电话",
"yys": "电信",
"kj": "2",
"address": "济南",
"time": "2026-02-05 18:30:00",
"status": 1,
"avatarType": 'image', // image 或 text
"notes": "合作伙伴"
},
{
"avatar": "",
"name": "",
"phone": "18818818818",
"phoneNotes": "电话",
"yys": "移动",
"kj": "2",
"address": "重庆",
"time": "2026-03-05 18:30:00",
"status": 4,
"avatarType": 'image', // image 或 text
"notes": "骚扰电话"
}
]);
const swiperList = ref([]);
const startX = ref(0);
const startY = ref(0);
const deleteWidth = ref(140);
const timepopup = ref();
const showEditModal = ref(false);
const editForm = reactive({
avatar: '',
avatarType: 'image', // image 或 text
name: '',
phone: '',
phoneNotes: '',
yys: '',
yysIndex: 0,
kj: '1',
address: '',
time: '',
status: 0,
notes: ''
});
const editIndex = ref(-1);
// 添加联系人相关
const showAddModal = ref(false);
const addForm = reactive({
avatar: '',
avatarType: 'image', // image 或 text
name: '',
phone: '',
phoneNotes: '',
yys: '',
kj: '1',
address: '',
time: '',
status: 0,
notes: ''
});
// 卡几选项
const kjOptions = ref([{
value: '1',
text: '卡1'
},
{
value: '2',
text: '卡2'
},
]);
// 状态选项(对象数组,用于显示中文文本)
const statusOptions = ref([{
value: 0,
text: '播出-未接'
},
{
value: 1,
text: '播出-已接'
},
{
value: 2,
text: '播出-拒接'
},
{
value: 3,
text: '来电-未接'
},
{
value: 4,
text: '来电-已接'
},
{
value: 5,
text: '来电-拒接'
}
]);
// 运营商选项
const yysOptions = ref([
{
value: '移动',
text: '移动'
},
{
value: '联通',
text: '联通'
},
{
value: '电信',
text: '电信'
},
{
value: '广电',
text: '广电'
}
]);
// 头像类型选项
const avatarTypeOptions = ref([{
value: 'image',
text: '图片头像'
},
{
value: 'text',
text: '文字头像'
}
]);
// 获取卡几文本
const getKjText = (value) => {
return value || '请选择';
};
// 获取状态文本
const getStatusText = (value) => {
const item = statusOptions.value.find(option => option.value === value.toString());
return item ? item.text : '请选择';
};
onMounted(() => {
let listArr = uni.getStorageSync('callLog')
if (Array.isArray(listArr) && listArr.length > 0) {
list.value = simpleSortByTime(listArr, 'time', true)
} else {
list.value = simpleSortByTime(list.value, 'time', true)
uni.setStorageSync('callLog', list.value)
}
swiperList.value = new Array(list.value.length).fill(0);
uni.$on('setActive', (status) => {
let listArr2 = uni.getStorageSync('callLog') || list.value
if (!status) {
listArr2 = listArr2.filter(item => item.status == 3)
}
if (Array.isArray(listArr2) && listArr2.length > 0) {
list.value = simpleSortByTime(listArr2, 'time', true)
} else {
list.value = simpleSortByTime(list.value, 'time', true)
}
swiperList.value = new Array(list.value.length).fill(0);
})
});
onUnload(() => {
uni.$off('setActive')
})
watch(() => props.isHuise, (newValue, oldValue) => {
console.log(newValue);
});
const touchStart = (e, index) => {
startX.value = e.changedTouches[0].clientX;
startY.value = e.changedTouches[0].clientY;
swiperList.value.forEach((item, i) => {
if (i !== index) {
swiperList.value[i] = 0;
}
});
};
const touchMove = (e, index) => {
const moveX = e.changedTouches[0].clientX;
const moveY = e.changedTouches[0].clientY;
const disX = moveX - startX.value;
const disY = moveY - startY.value;
if (Math.abs(disX) > Math.abs(disY)) {
e.preventDefault();
let distance = Math.max(-deleteWidth.value, Math.min(0, disX));
swiperList.value[index] = distance;
}
};
const touchEnd = (e, index) => {
const endX = e.changedTouches[0].clientX;
const disX = endX - startX.value;
if (disX < -deleteWidth.value / 2) {
swiperList.value[index] = -deleteWidth.value;
} else {
swiperList.value[index] = 0;
}
};
const deleteItem = (index) => {
uni.showModal({
title: '确认删除',
content: '确定要删除这条通话记录吗?',
confirmText: '删除',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
list.value.splice(index, 1);
swiperList.value.splice(index, 1);
uni.setStorageSync('callLog', list.value)
// emit('update:list', list.value);
}
}
});
};
const editItem = (index) => {
editIndex.value = index;
const item = list.value[index];
editForm.avatar = item.avatar || '';
editForm.avatarType = item.avatarType || 'image';
editForm.name = item.name || '';
editForm.phone = item.phone || '';
editForm.phoneNotes = item.phoneNotes || '';
editForm.yys = item.yys || '';
editForm.yysIndex = yysOptions.value.indexOf(item.yys || '移动');
editForm.kj = item.kj || '1';
editForm.address = item.address || '';
editForm.time = item.time || '';
editForm.status = item.status ||0;
editForm.notes = item.notes || '';
showEditModal.value = true;
};
function setTimeType() {
data.startDate = editForm.time
timepopup.value.open()
}
function onChangeStartDate(date) {
console.log('onChangeDate', date);
data.startDate = date
}
function settmes() {
timepopup.value.close()
// 检查当前是编辑还是添加模式
if (showEditModal.value) {
editForm.time = data.startDate
} else if (showAddModal.value) {
addForm.time = data.startDate
}
}
// 运营商选择变化处理
const onYysChange = (e) => {
const index = e.detail.value;
editForm.yys = yysOptions.value[index];
};
const closeEditModal = () => {
showEditModal.value = false;
editIndex.value = -1;
};
// 状态选择变化处理
const onStatusChange = (e) => {
const index = e.detail.value;
editForm.status = statusOptions.value[index].value;
};
// 卡几选择变化处理
const onKjChange = (e) => {
const index = e.detail.value;
editForm.kj = kjOptions.value[index];
};
const saveEdit = () => {
if (!editForm.name && !editForm.phone) {
uni.showToast({
title: '请填写姓名或电话',
icon: 'none'
});
return;
}
const item = list.value[editIndex.value];
item.avatar = editForm.avatar;
item.avatarType = editForm.avatarType;
item.title = editForm.title;
item.name = editForm.name;
item.phone = editForm.phone;
item.phoneNotes = editForm.phoneNotes;
item.yys = editForm.yys;
item.kj = editForm.kj;
item.address = editForm.address;
item.time = editForm.time;
item.icon = editForm.icon;
item.status = editForm.status;
item.notes = editForm.notes;
// emit('update:list', list.value);
closeEditModal();
list.value = simpleSortByTime(list.value, 'time', true)
uni.setStorageSync('callLog', list.value)
swiperList.value[editIndex.value] = 0;
};
// 添加联系人相关方法
const openAddModal = () => {
const now = new Date();
// 获取当前时间的年月日
const currentYear = now.getFullYear();
const currentMonth = now.getMonth();
const currentDay = now.getDate();
// 重置表单
addForm.avatar = '';
addForm.avatarType = 'image';
addForm.name = '';
addForm.phone = '';
addForm.phoneNotes = '电话';
addForm.yys = '';
addForm.kj = '1';
addForm.address = '';
addForm.time =
`${currentYear}-${currentMonth + 1}-${currentDay} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
addForm.status = 0;
addForm.notes = '';
showAddModal.value = true;
console.log(showAddModal.value)
};
const closeAddModal = () => {
showAddModal.value = false;
};
const saveAdd = () => {
// 验证必填项
if (!addForm.phone) {
uni.showToast({
title: '请输入电话',
icon: 'none'
});
return;
}
if (!addForm.yys) {
uni.showToast({
title: '请选择运营商',
icon: 'none'
});
return;
}
if (!addForm.time) {
uni.showToast({
title: '请输入时间',
icon: 'none'
});
return;
}
// if (!addForm.status) {
// uni.showToast({
// title: '请选择状态',
// icon: 'none'
// });
// return;
// }
// 创建新联系人
const newItem = {
avatar: addForm.avatar || '',
avatarType: addForm.avatarType || 'image',
title: addForm.name || addForm.phone,
name: addForm.name || '',
phone: addForm.phone,
phoneNotes: addForm.phoneNotes || '电话',
yys: addForm.yys,
kj: addForm.kj,
address: addForm.address || '',
time: addForm.time,
icon: addForm.icon || '',
status: addForm.status,
notes: addForm.notes || ''
};
// 添加到列表
list.value.unshift(newItem);
swiperList.value.unshift(0);
// emit('update:list', list.value);
closeAddModal();
list.value = simpleSortByTime(list.value, 'time', true)
uni.setStorageSync('callLog', list.value);
};
// 设置添加联系人时间
function setAddTimeType() {
data.startDate = addForm.time
timepopup.value.open()
}
// 添加联系人时的运营商选择变化处理
const onAddYysChange = (e) => {
addForm.yys = e.detail.value;
};
// 添加联系人时的状态选择变化处理
const onAddStatusChange = (e) => {
addForm.status = e.detail.value;
};
// 添加联系人时的卡几选择变化处理
const onAddKjChange = (e) => {
addForm.kj = e.detail.value;
};
// 头像类型选择变化处理
const onAvatarTypeChange = (e) => {
editForm.avatarType = e.detail.value;
};
// 添加联系人时的头像类型选择变化处理
const onAddAvatarTypeChange = (e) => {
addForm.avatarType = e.detail.value;
};
// 暴露方法给父组件
defineExpose({
openAddModal
});
/**
* 格式化时间显示
* @param {string|number|Date} time - 需要格式化的时间
* @returns {string} 格式化后的时间字符串
*/
const formatTime = (time) => {
// 如果传入的是空值,返回空字符串
if (!time) return '';
// 将传入的时间转换为Date对象
const date = new Date(time);
// 检查日期是否有效
if (isNaN(date.getTime())) {
return '';
}
const now = new Date();
// 获取传入时间的年月日
const targetYear = date.getFullYear();
const targetMonth = date.getMonth();
const targetDay = date.getDate();
// 获取当前时间的年月日
const currentYear = now.getFullYear();
const currentMonth = now.getMonth();
const currentDay = now.getDate();
// 判断是否为今天
if (targetYear === currentYear && targetMonth === currentMonth && targetDay === currentDay) {
// 今天:显示时分
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
return `${hours}:${minutes}`;
}
// 判断是否为昨天
const yesterday = new Date(now);
yesterday.setDate(currentDay - 1);
if (targetYear === yesterday.getFullYear() &&
targetMonth === yesterday.getMonth() &&
targetDay === yesterday.getDate()) {
return '昨天';
}
let ym = '-'
let md = '-'
let d = ''
if (props.type == 'ios' || props.type == 'oppo' || props.type == 'huawei') {
ym = '/'
md = '/'
} else {
ym = '年'
md = '月'
d = '日'
}
// 判断是否为今年
if (targetYear === currentYear) {
// 判断是否为近一周当前时间往前推7天内
const oneWeekAgo = new Date(now);
oneWeekAgo.setDate(currentDay - 6);
// 重置时间部分为0点只比较日期
const targetDateOnly = new Date(targetYear, targetMonth, targetDay);
const oneWeekAgoDateOnly = new Date(oneWeekAgo.getFullYear(), oneWeekAgo.getMonth(), oneWeekAgo.getDate());
const nowDateOnly = new Date(currentYear, currentMonth, currentDay);
if (targetDateOnly >= oneWeekAgoDateOnly && targetDateOnly <= nowDateOnly) {
// 近一周:显示星期几
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
return weekdays[date.getDay()];
} else {
// 今年但不在近一周:显示月-日
// 去年及以前:显示年-月-日.padStart(2, '0');
const year = targetYear.toString();
const month = (targetMonth + 1).toString()
const day = targetDay.toString()
return `${props.type == 'ios' ? year + ym : ''}${month}${md}${day}${d}`;
}
}
// 去年及以前:显示年-月-日
const year = targetYear.toString();
const month = (targetMonth + 1).toString()
const day = targetDay.toString()
return `${year}${ym}${month}${md}${day}${d}`;
}
/**
* 格式化字符串第一个3个字符后加空格之后每4个字符加一个空格
* @param {string} str - 需要格式化的字符串
* @returns {string} 格式化后的字符串
*/
function formatString(str) {
if (!str) return '';
// 移除字符串中可能存在的空格
const cleanStr = str.replace(/\s+/g, '');
if (cleanStr.length <= 3) {
return cleanStr;
}
// 第一个3个字符
let result = cleanStr.substring(0, 3);
// 剩余部分每4个字符一组
const remaining = cleanStr.substring(3);
for (let i = 0; i < remaining.length; i += 4) {
if (i > 0 || result.length > 0) {
result += ' ';
}
result += remaining.substring(i, i + 4);
}
return result;
}
function simpleSortByTime(arr, key = 'time', newestFirst = true) {
return [...arr].sort((a, b) => {
const timeA = new Date(a[key]).getTime();
const timeB = new Date(b[key]).getTime();
return newestFirst ? timeB - timeA : timeA - timeB;
});
}
function choose() {
uni.chooseImage({
count: 1,
sourceType: [ 'album' , 'camera'],
maxDuration: 30,
camera: 'back',
success(res) {
let tempFilePath = res.tempFiles[0].path
// 保存图片到本地
uni.saveFile({
tempFilePath: tempFilePath,
success: function(ress) {
// 使用本地保存的路径
const localPath = ress.savedFilePath;
if (showEditModal.value) {
editForm.avatar = localPath;
} else if (showAddModal.value) {
addForm.avatar = localPath;
}
// 显示成功提示
uni.showToast({
title: '图片保存成功',
icon: 'success'
});
},
fail: function(err) {
console.log('保存文件失败:', err);
// 如果保存失败,使用临时路径
if (showEditModal.value) {
editForm.avatar = tempFilePath;
} else if (showAddModal.value) {
addForm.avatar = tempFilePath;
}
uni.showToast({
title: '图片保存失败,使用临时路径',
icon: 'none'
});
}
});
},
fail(err) {
console.log('选择图片失败:', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
})
}
// 获取头像文字
const getAvatarText = (name) => {
if (!name) return '';
// 获取字符串长度(考虑中文字符)
const length = name.length;
if (length === 2) {
// 两个字:返回全部
return name;
} else {
// 返回第一个字符
return name.charAt(0);
}
};
// 获取头像文字
const getAvatarLength = (name) => {
const length = name.length;
return length
};
// 获取头像数字
const getAvatarNumber = (phone) => {
if (!phone) return '';
// 取电话号码的后四位
const cleanPhone = phone.replace(/\D/g, '');
return cleanPhone.slice(-4);
};
// 获取头像背景颜色
const getAvatarColor = (text) => {
if (!text) return '#999';
// 根据文本生成一个固定的颜色
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
];
let hash = 0;
for (let i = 0; i < text.length; i++) {
hash = text.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % colors.length;
return colors[index];
};
// 清除头像
const clearAvatar = () => {
if (showEditModal.value) {
editForm.avatar = '';
} else if (showAddModal.value) {
addForm.avatar = '';
}
};
// 生成随机数据
const generateRandomData = () => {
const names = ['张三', '李四', '王五', '赵六', '钱七', '孙八', '周九', '吴十'];
const addresses = ['北京', '上海', '广州', '深圳', '杭州', '成都', '武汉', '西安'];
const notes = ['工作往来', '老朋友', '推销电话', '快递小哥', '大学同学', '骚扰电话', '客户', '家人'];
// 生成随机电话号码
const randomPhone = () => {
const prefixes = ['130', '131', '132', '133', '134', '135', '136', '137', '138', '139', '150', '151', '152',
'153', '155', '156', '157', '158', '159', '170', '171', '172', '173', '175', '176', '177', '178', '180',
'181', '182', '183', '184', '185', '186', '187', '188', '189'
];
const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
const suffix = Math.floor(Math.random() * 100000000).toString().padStart(8, '0');
return prefix + suffix;
};
// 生成随机时间
const randomTime = () => {
const now = new Date();
const randomDays = Math.floor(Math.random() * 30); // 30天内的随机时间
const randomDate = new Date(now.getTime() - randomDays * 24 * 60 * 60 * 1000);
const year = randomDate.getFullYear();
const month = (randomDate.getMonth() + 1).toString().padStart(2, '0');
const day = randomDate.getDate().toString().padStart(2, '0');
const hours = randomDate.getHours().toString().padStart(2, '0');
const minutes = randomDate.getMinutes().toString().padStart(2, '0');
const seconds = randomDate.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 填充随机数据
addForm.name = names[Math.floor(Math.random() * names.length)];
addForm.phone = randomPhone();
addForm.phoneNotes = '电话';
addForm.yys = yysOptions.value[Math.floor(Math.random() * yysOptions.value.length)].value;
addForm.kj = Math.random() > 0.5 ? '1' : '2';
addForm.address = addresses[Math.floor(Math.random() * addresses.length)];
addForm.time = randomTime();
addForm.status = Math.floor(Math.random() * 6);
addForm.notes = notes[Math.floor(Math.random() * notes.length)];
addForm.avatar = ''; // 随机数据不生成头像
addForm.avatarType = Math.random() > 0.5 ? 'image' : 'text'; // 随机选择头像类型
};
</script>
<style lang="scss" scoped>
.call-list {
width: 100vw;
overflow: hidden;
box-sizing: border-box;
background-color: #fff;
.item {
display: flex;
position: relative;
width: 100%;
.avatar {
width: 80rpx;
height: 80rpx;
image {
width: 100% !important;
height: 100% !important;
}
}
.infoBox {
width: calc(100% - 80rpx);
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
z-index: 2;
.left-box {
display: flex;
justify-content: flex-start;
align-items: center;
.leftInfo {
.title {
font-size: 32rpx;
color: #333;
margin-bottom: 10rpx;
}
.info {
display: flex;
font-size: 28rpx;
color: #666;
view {
margin-right: 10rpx;
}
.kj {
line-height: 0;
width: 22rpx;
height: 22rpx;
image {
width: 100%;
height: 100%;
}
}
.status-icon {
line-height: 0;
width: 22rpx;
height: 22rpx;
image {
width: 100%;
height: 100%;
}
}
}
}
}
.right-box {
display: flex;
justify-content: flex-end;
align-items: center;
.time {
font-size: 28rpx;
color: #666;
}
.icon {
width: 40rpx;
height: 40rpx;
overflow: hidden;
margin-left: 20rpx;
image {
width: 100%;
height: 100%;
}
}
}
}
.edit-btn {
position: absolute;
top: 0;
right: -140px;
width: 70px;
height: 100%;
background-color: #007AFF;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
.edit-text {
color: #fff;
font-size: 28rpx;
}
}
.delete-btn {
position: absolute;
top: 0;
right: -70px;
width: 70px;
height: 100%;
background-color: #FF3B30;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
.delete-text {
color: #fff;
font-size: 28rpx;
}
}
}
}
.call-list-ios {
.item {
align-items: center;
justify-content: space-between;
height: 132rpx;
}
.avatar {
width: 84rpx;
height: 84rpx;
border-radius: 50%;
margin-left: 32rpx;
overflow: hidden;
}
.infoBox {
width: calc(100% - 136rpx) !important;
padding: 24rpx 32rpx 24rpx 0;
height: 100%;
box-sizing: border-box;
box-shadow: inset 0 -0.3px 0 0 #C2C2C2;
.left-box {
.title {
font-weight: 400;
font-size: 16px;
color: #1A1A1A;
line-height: 16px;
}
.title-red {
color: #FC3E30 !important;
}
.info {
align-items: center;
}
.info .phone {
font-weight: 400;
font-size: 14px;
color: #838383;
line-height: 20px;
}
.info .yys {
height: 12px;
background: #C7C7C7;
border-radius: 2px 2px 2px 2px;
padding: 0 4rpx;
font-weight: 400;
font-size: 10px;
color: #FFFFFF;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
}
.right-box {
.time {
font-weight: 400;
font-size: 14px;
color: #838383;
line-height: 14px;
}
.icon {
width: 40rpx;
height: 40rpx;
overflow: hidden;
margin-left: 14rpx;
image {
width: 100%;
height: 100%;
}
}
}
}
}
.call-list-xiaomi {
.item {
height: 140rpx;
}
.infoBox {
width: 100% !important;
padding: 34rpx 56rpx 28rpx 56rpx;
height: 100%;
box-sizing: border-box;
.title {
font-weight: 400;
font-size: 16px;
color: #1A1A1A;
text {
font-size: 13px !important;
color: #767676 !important;
}
}
.title-red {
color: #EE0115 !important;
}
.info {
view {
font-size: 13px;
color: #767676;
}
}
}
}
.call-list-oppo {
.item {
padding: 32rpx 36rpx 0 36rpx !important;
justify-content: space-between;
height: 140rpx;
}
.infoBox {
width: calc(100% - 46rpx) !important;
padding-bottom: 36rpx;
height: 100%;
box-sizing: border-box;
box-shadow: inset 0 -0.3px 0 0 #C2C2C2;
.title {
font-weight: 400;
font-size: 16px;
color: #1A1A1A;
}
.title-red {
color: #DB2C22 !important;
}
.info {
view {
font-size: 13px;
color: #767676;
}
.notes {
color: #F17A30;
}
}
.right-box {
.time {
color: #767676;
font-size: 13px;
}
}
}
.avatar {
width: 26rpx !important;
height: 26rpx !important;
border-radius: 0 !important;
image {
width: 100% !important;
height: 100% !important;
}
}
.info {
display: flex;
align-items: center;
}
}
.call-list-vivo {
.item {
padding: 36rpx 52rpx 0 36rpx !important;
justify-content: space-between;
height: 140rpx;
.infoBox {
width: calc(100% - 52rpx) !important;
padding-bottom: 32rpx;
height: 100%;
box-sizing: border-box;
box-shadow: inset 0 -0.3px 0 0 #C2C2C2;
.title {
font-weight: 400;
font-size: 16px;
color: #1A1A1A;
}
.title-red {
color: #F04E51 !important;
}
.info {
view {
font-size: 12px;
color: #8C8C8C;
}
}
.right-box {
.time {
color: #8C8C8C;
font-size: 14px;
}
}
}
}
.avatar {
width: 32rpx !important;
height: 32rpx !important;
border-radius: 0 !important;
image {
width: 100% !important;
height: 100% !important;
}
}
}
.call-list-huawei {
.item {
padding: 24rpx 32rpx 0 32rpx !important;
justify-content: space-between;
height: 120rpx;
}
.infoBox {
width: calc(100% - 52rpx) !important;
padding-bottom: 24rpx;
height: 100%;
box-sizing: border-box;
box-shadow: inset 0 -0.3px 0 0 #C2C2C2;
.title {
font-weight: 400;
font-size: 16px;
color: #1A1A1A;
}
.title-red {
color: #E83F28 !important;
}
.info {
view {
font-size: 13px;
color: #696969;
}
}
.right-box {
.time {
color: #696969;
font-size: 13px;
}
}
}
.avatar {
width: 32rpx !important;
height: 32rpx !important;
border-radius: 0 !important;
image {
width: 100% !important;
height: 100% !important;
}
}
.info {
display: flex;
align-items: center;
}
}
.modal-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}
.modal-content {
width: 600rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
padding: 40rpx;
text-align: center;
border-bottom: 1rpx solid #f0f0f0;
}
.modal-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.modal-body {
padding: 40rpx;
height: calc(80vh - 260rpx);
overflow-x: scroll;
}
.form-item {
margin-bottom: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-label {
display: block;
font-size: 28rpx;
color: #666;
margin-bottom: 16rpx;
text{
color: red;
}
}
.form-input {
width: 364rpx;
height: 80rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 0 20rpx;
font-size: 28rpx;
box-sizing: border-box;
color: #1A1A1A;
}
.form-select{
width: 364rpx !important;
flex: none;
}
.form-avatar-container {
display: flex;
align-items: center;
}
.suiji{
width: 50rpx;
height: 50rpx;
}
.form-avatar {
width: 80px;
height: 80px;
border: 1px dashed #666;
image {
width: 100%;
height: 100%;
}
}
.form-text-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 40rpx;
font-weight: bold;
background: linear-gradient(180deg, #A1A8B8 0%, #878B94 100%);
border: 1px dashed #666;
}
.avatar-clear {
margin-left: 20rpx;
color: #007AFF;
font-size: 28rpx;
cursor: pointer;
}
.text-avatar {
width: 84rpx;
height: 84rpx;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 40rpx;
font-weight: bold;
background: linear-gradient(180deg, #A1A8B8 0%, #878B94 100%);
}
.text-avatar-18{
font-size: 36rpx !important;
}
.form-input-time {
line-height: 40px;
}
.form-picker {
width: 100%;
height: 80rpx;
border: 1rpx solid #ddd;
border-radius: 8rpx;
padding: 0 20rpx;
box-sizing: border-box;
}
.form-picker-content {
height: 100%;
font-size: 28rpx;
color: #333;
}
.modal-footer {
display: flex;
border-top: 1rpx solid #f0f0f0;
height: 120rpx;
}
.btn {
flex: 1;
height: 100rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 32rpx;
background: #F1F1F1;
margin: 10rpx;
border-radius: 6px 6px 6px 6px;
}
.btn-random {
border-right: 1rpx solid #f0f0f0;
background: #FF9500;
color: #fff;
}
.btn-cancel {
border-right: 1rpx solid #f0f0f0;
color: #666;
}
.btn-confirm {
color: #fff;
background: #007AFF;
}
.timeBox {
background-color: #fff;
.titleBox {
padding: 10px;
display: flex;
justify-content: flex-end;
.btns {
font-size: 12px;
color: #007AFF;
}
}
}
</style>