610 lines
14 KiB
Vue
610 lines
14 KiB
Vue
<template>
|
||
<view>
|
||
<MessageNavBar :phone="data.phone" :isScroll="data.isScroll" @add="openAddPopup" @setSim="setSim">
|
||
<MessageList :phone="data.phone" :list="defaultList" @item-click="itemClick" @delete-item="deleteItem"
|
||
@edit-item="editItem">
|
||
</MessageList>
|
||
</MessageNavBar>
|
||
|
||
<!-- 添加短信弹窗 -->
|
||
<view v-if="showAddPopup" class="add-mask" @tap="closeAddPopup">
|
||
<view class="add-popup" @tap.stop>
|
||
<view class="add-header">{{ editingItem ? '编辑短信' : '新建短信' }}</view>
|
||
<view class="add-body">
|
||
<view class="add-row">
|
||
<text class="add-label">头像URL:</text>
|
||
<view class="image-box" :class="addForm.imgShape" style="width: 84rpx;height: 84rpx;"
|
||
@tap="selectImage">
|
||
<image v-if="addForm.img" class="image w100 h100" :src="addForm.img" mode="aspectFill">
|
||
</image>
|
||
<image v-else class="image w100 h100" src="/static/image/phone-message/add.png"
|
||
mode="aspectFill">
|
||
</image>
|
||
</view>
|
||
<view v-if="data.phone == 'iphone'"
|
||
style="flex: 1;display: flex;align-items: center;justify-content: flex-end;">
|
||
<uni-data-checkbox style="display: flex;justify-content: flex-end;"
|
||
v-model="addForm.imgShape" :localdata="shape"></uni-data-checkbox>
|
||
</view>
|
||
</view>
|
||
<view class="add-row">
|
||
<text class="add-label required">联系人</text>
|
||
<input class="add-input" v-model="addForm.title" placeholder="请输入名称或号码" />
|
||
</view>
|
||
<view class="add-row between" v-if="data.phone == 'iphone'">
|
||
<text class="add-label">取消通知</text>
|
||
<switch :checked="addForm.noNotice" @change="addForm.noNotice = !addForm.noNotice" />
|
||
</view>
|
||
<view class="add-row between">
|
||
<text class="add-label">是否未读</text>
|
||
<switch :checked="addForm.unRead" @change="addForm.unRead = !addForm.unRead" />
|
||
</view>
|
||
<view class="add-row"
|
||
v-if="addForm.unRead && (data.phone == 'oppo' || data.phone == 'huawei' || data.phone == 'iphone')">
|
||
<text class="add-label">未读数量</text>
|
||
<input class="add-input" type="number" v-model="addForm.unReadNumber" placeholder="请输入未读数量" />
|
||
</view>
|
||
<view class="add-row" v-if="addForm.chatList.length == 0 && editingItem">
|
||
<text class="add-label">消息时间</text>
|
||
<view class="time-picker-group">
|
||
<picker mode="date" :value="addForm.date" @change="onAddDateChange">
|
||
<view class="time-picker-item">
|
||
<text>{{ addForm.date || '选择日期' }}</text>
|
||
</view>
|
||
</picker>
|
||
<picker mode="time" :value="addForm.timeOfDay" @change="onAddTimeChange">
|
||
<view class="time-picker-item">
|
||
<text>{{ addForm.timeOfDay || '选择时刻' }}</text>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="add-footer">
|
||
<view class="add-btn cancel" @tap="closeAddPopup">取消</view>
|
||
<view class="add-btn confirm" @tap="confirmAdd">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 设置运营商弹窗 -->
|
||
<view v-if="showSimPopup" class="add-mask" @tap="closeSimPopup">
|
||
<view class="add-popup" @tap.stop>
|
||
<view class="add-header">设置卡1卡2运营商</view>
|
||
<view class="add-body">
|
||
<view class="add-row">
|
||
<text class="add-label">卡1运营商</text>
|
||
<input class="add-input" v-model="simForm.sim1" placeholder="请输入卡1运营商名称" />
|
||
</view>
|
||
<view class="add-row">
|
||
<text class="add-label">卡2运营商</text>
|
||
<input class="add-input" v-model="simForm.sim2" placeholder="请输入卡2运营商名称" />
|
||
</view>
|
||
</view>
|
||
<view class="add-footer">
|
||
<view class="add-btn cancel" @tap="closeSimPopup">取消</view>
|
||
<view class="add-btn confirm" @tap="confirmSim">确定</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import MessageNavBar from '@/components/message/list/message-nav-bar.vue'
|
||
import MessageList from '@/components/message/list/list.vue'
|
||
import defaultData from './defaultData.json'
|
||
import {
|
||
ref,
|
||
reactive
|
||
} from 'vue'
|
||
import {
|
||
onLoad,
|
||
onShow,
|
||
onPageScroll
|
||
} from "@dcloudio/uni-app";
|
||
import {
|
||
stringUtil,
|
||
util
|
||
} from '@/utils/common.js';
|
||
|
||
const defaultList = ref(defaultData)
|
||
|
||
const data = reactive({
|
||
navBar: {
|
||
title: '信息',
|
||
bgColor: '#FFFFFF',
|
||
},
|
||
phone: 'iphone',
|
||
isScroll: false
|
||
})
|
||
|
||
const shape = [{
|
||
text: '圆形',
|
||
value: 'circle'
|
||
}, {
|
||
text: '方形',
|
||
value: 'square',
|
||
}]
|
||
|
||
const STORAGE_KEY = 'message_list'
|
||
const SIM_STORAGE_KEY = 'sim_info'
|
||
|
||
onLoad((options) => {
|
||
if (options.phone) {
|
||
data.phone = options.phone
|
||
}
|
||
|
||
})
|
||
|
||
onShow(() => {
|
||
// #ifdef APP-PLUS
|
||
if (data.phone == 'oppo') {
|
||
util.setAndroidSystemBarColor('#FAFAFA')
|
||
} else {
|
||
util.setAndroidSystemBarColor('#ffffff')
|
||
}
|
||
setTimeout(() => {
|
||
plus.navigator.setStatusBarStyle("dark");
|
||
}, 500)
|
||
// #endif
|
||
// 优先从缓存加载列表
|
||
try {
|
||
const cached = uni.getStorageSync(STORAGE_KEY)
|
||
if (cached) {
|
||
defaultList.value = JSON.parse(cached)
|
||
}
|
||
} catch (e) { }
|
||
})
|
||
|
||
|
||
onPageScroll((e) => {
|
||
if (e.scrollTop > 60) {
|
||
data.isScroll = true
|
||
} else {
|
||
data.isScroll = false
|
||
}
|
||
})
|
||
|
||
/**
|
||
* 将图片保存到本地持久化存储
|
||
* @param {string} tempFilePath 临时文件路径
|
||
* @returns {Promise<string>} 持久化后的文件路径
|
||
*/
|
||
const saveImageToLocal = (tempFilePath) => {
|
||
return new Promise((resolve, reject) => {
|
||
// 如果是已经持久化的路径或是静态资源,直接返回
|
||
if (!tempFilePath || tempFilePath.startsWith('_doc') || tempFilePath.startsWith('/static')) {
|
||
return resolve(tempFilePath)
|
||
}
|
||
uni.saveFile({
|
||
tempFilePath: tempFilePath,
|
||
success: (res) => {
|
||
console.log('图片持久化成功:', res.savedFilePath)
|
||
resolve(res.savedFilePath)
|
||
},
|
||
fail: (err) => {
|
||
console.error('图片持久化失败:', err)
|
||
resolve(tempFilePath) // 失败则回退使用原路径,确保业务不中断
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 删除本地持久化文件
|
||
* @param {string} filePath 文件路径
|
||
*/
|
||
const removeLocalFile = (filePath) => {
|
||
if (filePath && filePath.startsWith('_doc')) {
|
||
uni.removeSavedFile({
|
||
filePath: filePath,
|
||
success: () => {
|
||
console.log('本地文件删除成功:', filePath)
|
||
},
|
||
fail: (err) => {
|
||
console.warn('本地文件删除失败:', filePath, err)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 删除元素
|
||
* @param item
|
||
*/
|
||
const deleteItem = (item) => {
|
||
uni.showModal({
|
||
title: '提示',
|
||
content: '确定要删除吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
const idx = defaultList.value.findIndex(i => i.id === item.id)
|
||
if (idx > -1) {
|
||
// 删除关联的本地图片文件
|
||
if (item.img) {
|
||
removeLocalFile(item.img)
|
||
}
|
||
defaultList.value.splice(idx, 1)
|
||
uni.setStorageSync(STORAGE_KEY, JSON.stringify(defaultList.value))
|
||
}
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 选择图片
|
||
*/
|
||
const selectImage = () => {
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sizeType: ['original', 'compressed'],
|
||
sourceType: ['album', 'camera'],
|
||
success: (res) => {
|
||
addForm.img = res.tempFilePaths[0]
|
||
}
|
||
})
|
||
}
|
||
|
||
const itemClick = (item) => {
|
||
util.goPage(`/pages/message/chat-page/chat-page?phone=${data.phone}&id=${item.id}`)
|
||
}
|
||
|
||
// ===== 添加短信弹窗 =====
|
||
const showAddPopup = ref(false)
|
||
const addForm = reactive({
|
||
title: '',
|
||
img: '',
|
||
content: '',
|
||
date: '',
|
||
timeOfDay: ''
|
||
})
|
||
|
||
// 弹窗模式:null=新增,有值=编辑中的条目
|
||
const editingItem = ref(null)
|
||
|
||
const openAddPopup = () => {
|
||
// 重置表单,默认时间为当前
|
||
const now = new Date()
|
||
const pad = v => String(v).padStart(2, '0')
|
||
editingItem.value = null
|
||
addForm.title = ''
|
||
addForm.img = ''
|
||
addForm.content = ''
|
||
addForm.unRead = false
|
||
addForm.unReadNumber = 1
|
||
addForm.noNotice = false
|
||
addForm.chatList = []
|
||
addForm.imgShape = 'circle'
|
||
addForm.date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`
|
||
addForm.timeOfDay = `${pad(now.getHours())}:${pad(now.getMinutes())}`
|
||
showAddPopup.value = true
|
||
}
|
||
|
||
const closeAddPopup = () => {
|
||
showAddPopup.value = false
|
||
}
|
||
|
||
const onAddDateChange = (e) => {
|
||
addForm.date = e.detail.value
|
||
}
|
||
|
||
const onAddTimeChange = (e) => {
|
||
addForm.timeOfDay = e.detail.value
|
||
}
|
||
|
||
/**
|
||
* 编辑元素:回填表单并打开弹窗
|
||
*/
|
||
const editItem = (item) => {
|
||
editingItem.value = item
|
||
const lastMsg = item.chatList && item.chatList.length ? item.chatList[item.chatList.length - 1] : null
|
||
const timeStr = lastMsg ? lastMsg.time : (item.time || '')
|
||
const parts = timeStr.split(' ')
|
||
addForm.title = item.title || ''
|
||
addForm.img = item.img || ''
|
||
addForm.imgShape = item.imgShape || 'circle'
|
||
addForm.content = ''
|
||
addForm.unRead = !!item.unRead
|
||
addForm.unReadNumber = item.unReadNumber || 0
|
||
addForm.noNotice = item.noNotice || false
|
||
addForm.date = parts[0] || ''
|
||
addForm.timeOfDay = parts[1] || ''
|
||
addForm.chatList = item.chatList || []
|
||
showAddPopup.value = true
|
||
}
|
||
|
||
const confirmAdd = async () => {
|
||
if (!addForm.title.trim()) {
|
||
uni.showToast({ title: '请输入联系人名称', icon: 'none' })
|
||
return
|
||
}
|
||
|
||
const time = `${addForm.date} ${addForm.timeOfDay}`.trim()
|
||
|
||
// 处理图片持久化
|
||
let finalImgPath = addForm.img
|
||
if (editingItem.value) {
|
||
// 编辑模式:如果图片发生了变化,保存新图并删除旧图
|
||
if (addForm.img !== editingItem.value.img) {
|
||
finalImgPath = await saveImageToLocal(addForm.img)
|
||
removeLocalFile(editingItem.value.img)
|
||
}
|
||
} else {
|
||
// 新增模式:直接持久化
|
||
finalImgPath = await saveImageToLocal(addForm.img)
|
||
}
|
||
|
||
if (editingItem.value) {
|
||
// ===== 编辑模式:更新已有条目 =====
|
||
const idx = defaultList.value.findIndex(i => i.id === editingItem.value.id)
|
||
if (idx > -1) {
|
||
defaultList.value[idx].title = addForm.title.trim()
|
||
defaultList.value[idx].img = finalImgPath
|
||
defaultList.value[idx].unRead = addForm.unRead
|
||
defaultList.value[idx].unReadNumber = addForm.unReadNumber
|
||
defaultList.value[idx].noNotice = addForm.noNotice
|
||
defaultList.value[idx].imgShape = addForm.imgShape
|
||
defaultList.value[idx].time = time
|
||
}
|
||
} else {
|
||
// ===== 新增模式 =====
|
||
const newItem = {
|
||
id: Date.now(),
|
||
unRead: addForm.unRead,
|
||
unReadNumber: addForm.unReadNumber,
|
||
noNotice: addForm.noNotice,
|
||
img: finalImgPath,
|
||
title: addForm.title.trim(),
|
||
imgShape: addForm.imgShape,
|
||
chatList: addForm.content.trim() ? [
|
||
{
|
||
id: stringUtil.uuid(),
|
||
time: time,
|
||
content: `<p>${addForm.content.trim()}</p>`,
|
||
isMe: false
|
||
}
|
||
] : [],
|
||
time: time
|
||
}
|
||
defaultList.value.unshift(newItem)
|
||
}
|
||
|
||
// 按最新消息时间降序排列
|
||
defaultList.value.sort((a, b) => {
|
||
const timeA = new Date(((a.chatList && a.chatList.length ? a.chatList[a.chatList.length - 1].time : null) || a.time || '').replace(/-/g, '/')).getTime()
|
||
const timeB = new Date(((b.chatList && b.chatList.length ? b.chatList[b.chatList.length - 1].time : null) || b.time || '').replace(/-/g, '/')).getTime()
|
||
return timeB - timeA
|
||
})
|
||
try {
|
||
uni.setStorageSync(STORAGE_KEY, JSON.stringify(defaultList.value))
|
||
} catch (e) { }
|
||
closeAddPopup()
|
||
}
|
||
|
||
// ===== 设置运营商弹窗 =====
|
||
const showSimPopup = ref(false)
|
||
const simForm = reactive({
|
||
sim1: '',
|
||
sim2: ''
|
||
})
|
||
|
||
const setSim = () => {
|
||
try {
|
||
const cached = uni.getStorageSync(SIM_STORAGE_KEY)
|
||
const simInfo = cached ? JSON.parse(cached) : { sim1: '中国电信', sim2: '中国移动' }
|
||
simForm.sim1 = simInfo.sim1
|
||
simForm.sim2 = simInfo.sim2
|
||
} catch (e) {
|
||
simForm.sim1 = '中国电信'
|
||
simForm.sim2 = '中国移动'
|
||
}
|
||
showSimPopup.value = true
|
||
}
|
||
|
||
const closeSimPopup = () => {
|
||
showSimPopup.value = false
|
||
}
|
||
|
||
const confirmSim = () => {
|
||
try {
|
||
uni.setStorageSync(SIM_STORAGE_KEY, JSON.stringify({
|
||
sim1: simForm.sim1.trim() || '联通',
|
||
sim2: simForm.sim2.trim() || '移动'
|
||
}))
|
||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||
} catch (e) { }
|
||
closeSimPopup()
|
||
}
|
||
|
||
</script>
|
||
|
||
<style>
|
||
@import '@/common/main.css';
|
||
|
||
page {
|
||
background-color: #FFFFFF;
|
||
}
|
||
</style>
|
||
<style lang="less">
|
||
::v-deep .uni-navbar__header-btns {
|
||
width: 100px !important;
|
||
flex: 1;
|
||
}
|
||
|
||
.iphone-style {
|
||
.mg-r-30 {
|
||
margin-right: 60rpx;
|
||
}
|
||
|
||
.mg-r-5 {
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.left-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.right-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
}
|
||
|
||
.left-text {
|
||
font-size: 32rpx;
|
||
color: #0278E2;
|
||
margin-left: 10rpx;
|
||
}
|
||
|
||
.center-text {
|
||
font-size: 32rpx;
|
||
color: #1a1a1a;
|
||
}
|
||
}
|
||
|
||
.mi-style {
|
||
.right-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
margin-right: 30rpx;
|
||
}
|
||
}
|
||
|
||
/* ===== 添加短信弹窗 ===== */
|
||
.add-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
z-index: 999;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
}
|
||
|
||
.add-popup {
|
||
width: 88%;
|
||
background-color: #ffffff;
|
||
border-radius: 24rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.add-header {
|
||
padding: 36rpx 40rpx 20rpx;
|
||
font-size: 34rpx;
|
||
font-weight: bold;
|
||
color: #1A1A1A;
|
||
text-align: center;
|
||
}
|
||
|
||
.add-body {
|
||
padding: 8rpx 0;
|
||
}
|
||
|
||
.add-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 20rpx 32rpx;
|
||
gap: 16rpx;
|
||
|
||
.image-box {
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.circle {
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.square {
|
||
border-radius: 16rpx;
|
||
}
|
||
}
|
||
|
||
.between {
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.required {
|
||
position: relative;
|
||
}
|
||
|
||
.required::before {
|
||
position: absolute;
|
||
left: -10px;
|
||
content: '*';
|
||
top: 0;
|
||
color: #EA0000;
|
||
}
|
||
|
||
.add-label {
|
||
font-size: 28rpx;
|
||
color: #1A1A1A;
|
||
width: 150rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.add-input {
|
||
flex: 1;
|
||
height: 70rpx;
|
||
background-color: #F6F6F6;
|
||
border-radius: 14rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
color: #1A1A1A;
|
||
|
||
::v-deep .uni-input {
|
||
color: #aaaaaa;
|
||
}
|
||
}
|
||
|
||
.add-footer {
|
||
padding: 32rpx 24rpx;
|
||
display: flex;
|
||
}
|
||
|
||
.add-btn {
|
||
flex: 1;
|
||
height: 90rpx;
|
||
line-height: 90rpx;
|
||
text-align: center;
|
||
font-size: 32rpx;
|
||
background-color: #F1F1F1;
|
||
margin: 0 16rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.add-btn.cancel {
|
||
color: #767676;
|
||
}
|
||
|
||
.add-btn.confirm {
|
||
color: #fff;
|
||
background-color: #1777FF;
|
||
}
|
||
|
||
.time-picker-group {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 16rpx;
|
||
flex: 1;
|
||
}
|
||
|
||
.time-picker-item {
|
||
flex: 1;
|
||
height: 70rpx;
|
||
line-height: 70rpx;
|
||
background-color: #F8F8F8;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
text-align: center;
|
||
}
|
||
</style>
|