|
@@ -0,0 +1,387 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="detail-page">
|
|
|
|
|
+ <!-- 头部 -->
|
|
|
|
|
+ <view class="detail-header">
|
|
|
|
|
+ <view class="header-top">
|
|
|
|
|
+ <text class="back-btn" @click="goBack">← 返回</text>
|
|
|
|
|
+ <text class="page-title">关系详情</text>
|
|
|
|
|
+ <text class="edit-btn" @click="toggleEdit">{{ isEditing ? '取消' : '编辑' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="header-avatar" :style="{ background: avatarColor }">
|
|
|
|
|
+ <text class="avatar-text">{{ initial }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="header-name">{{ contact.name || '未知' }}</text>
|
|
|
|
|
+ <text class="header-meta">{{ typeLabel }}</text>
|
|
|
|
|
+ <text class="header-known" v-if="contact.knownSinceLabel">{{ contact.knownSinceLabel }}</text>
|
|
|
|
|
+ <view class="header-intimacy">
|
|
|
|
|
+ <text class="intimacy-heart">❤️</text>
|
|
|
|
|
+ <text class="intimacy-num">{{ contact.intimacyLevel || 0 }}</text>
|
|
|
|
|
+ <text class="intimacy-label">亲密度</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 基本信息 -->
|
|
|
|
|
+ <view class="info-section">
|
|
|
|
|
+ <text class="section-title">基本信息</text>
|
|
|
|
|
+ <view class="info-row" v-if="!isEditing">
|
|
|
|
|
+ <text class="info-icon">📞</text>
|
|
|
|
|
+ <text class="info-value">{{ contact.phone || '未填写' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="info-row" v-if="!isEditing && contact.birthdayLabel">
|
|
|
|
|
+ <text class="info-icon">🎂</text>
|
|
|
|
|
+ <text class="info-value">{{ contact.birthdayLabel }}
|
|
|
|
|
+ <text v-if="contact.birthdayCountdown" class="countdown"> · {{ contact.birthdayCountdown }}</text>
|
|
|
|
|
+ </text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="info-row" v-if="!isEditing && contact.bio">
|
|
|
|
|
+ <text class="info-icon">📝</text>
|
|
|
|
|
+ <text class="info-value">{{ contact.bio }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="info-row" v-if="!isEditing && contact.tags">
|
|
|
|
|
+ <text class="info-icon">🏷️</text>
|
|
|
|
|
+ <text class="info-value">{{ contact.tags }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="info-row" v-if="!isEditing && contact.notes">
|
|
|
|
|
+ <text class="info-icon">💬</text>
|
|
|
|
|
+ <text class="info-value">{{ contact.notes }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 编辑模式 -->
|
|
|
|
|
+ <view v-if="isEditing">
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">姓名</text>
|
|
|
|
|
+ <input class="edit-input" v-model="editForm.name" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">手机号</text>
|
|
|
|
|
+ <input class="edit-input" v-model="editForm.phone" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">关系类型</text>
|
|
|
|
|
+ <picker mode="selector" :range="typeOptions" @change="onTypeChange">
|
|
|
|
|
+ <view class="edit-picker">{{ editForm.relationshipTypeLabel || '请选择' }}</view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">认识时间</text>
|
|
|
|
|
+ <picker mode="date" @change="onDateChange">
|
|
|
|
|
+ <view class="edit-picker">{{ editForm.knownSince || '请选择' }}</view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">生日</text>
|
|
|
|
|
+ <picker mode="date" @change="onBirthdayChange">
|
|
|
|
|
+ <view class="edit-picker">{{ editForm.birthday || '请选择' }}</view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">个人介绍</text>
|
|
|
|
|
+ <input class="edit-input" v-model="editForm.bio" placeholder="选填" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="edit-row">
|
|
|
|
|
+ <text class="edit-label">备注</text>
|
|
|
|
|
+ <input class="edit-input" v-model="editForm.notes" placeholder="选填" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <button class="btn-save" @click="saveEdit">保存</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 互动记录 -->
|
|
|
|
|
+ <view class="interaction-section">
|
|
|
|
|
+ <text class="section-title">互动记录</text>
|
|
|
|
|
+ <view class="interaction-item" v-if="contact.lastContactAt">
|
|
|
|
|
+ <text class="interaction-date">{{ formatDate(contact.lastContactAt) }}</text>
|
|
|
|
|
+ <text class="interaction-desc">标记了联系</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="interaction-item" v-if="contact.createdAt">
|
|
|
|
|
+ <text class="interaction-date">{{ formatDate(contact.createdAt) }}</text>
|
|
|
|
|
+ <text class="interaction-desc">添加了联系人</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="interaction-empty" v-if="!contact.lastContactAt && !contact.updatedAt">
|
|
|
|
|
+ <text>暂无互动记录</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <button class="btn-interaction" @click="markInteraction">📞 标记联系</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 删除 -->
|
|
|
|
|
+ <view class="danger-section">
|
|
|
|
|
+ <button class="btn-delete" @click="confirmDelete">删除联系人</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import { getContactDetail, updateContact, deleteContact, recordContactInteraction } from '../../utils/api.js'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ data: function() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ contactId: null,
|
|
|
|
|
+ contact: {},
|
|
|
|
|
+ isEditing: false,
|
|
|
|
|
+ editForm: {},
|
|
|
|
|
+ typeOptions: ['家人', '朋友', '伴侣', '同事', '其他'],
|
|
|
|
|
+ typeValues: { '家人': 'family', '朋友': 'friend', '伴侣': 'partner', '同事': 'colleague', '其他': 'other' },
|
|
|
|
|
+ reverseTypeValues: { 'family': '家人', 'friend': '朋友', 'partner': '伴侣', 'colleague': '同事', 'other': '其他' }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ computed: {
|
|
|
|
|
+ initial: function() {
|
|
|
|
|
+ return this.contact.name ? this.contact.name[0] : '?'
|
|
|
|
|
+ },
|
|
|
|
|
+ avatarColor: function() {
|
|
|
|
|
+ var colors = ['#FF6B35', '#667eea', '#10B981', '#F59E0B', '#8B5CF6', '#EC4899']
|
|
|
|
|
+ var idx = (this.contact.id || 0) % colors.length
|
|
|
|
|
+ return colors[idx]
|
|
|
|
|
+ },
|
|
|
|
|
+ typeLabel: function() {
|
|
|
|
|
+ var map = { family: '家人', friend: '朋友', partner: '伴侣', colleague: '同事', other: '其他' }
|
|
|
|
|
+ return map[this.contact.relationshipType] || '其他'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onLoad: function(options) {
|
|
|
|
|
+ if (options.id) {
|
|
|
|
|
+ this.contactId = options.id
|
|
|
|
|
+ this.loadDetail()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ loadDetail: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (!this.contactId) return
|
|
|
|
|
+ getContactDetail(this.contactId).then(function(res) {
|
|
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
|
|
+ self.contact = res.data
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: res.message || '加载失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleEdit: function() {
|
|
|
|
|
+ if (this.isEditing) {
|
|
|
|
|
+ this.isEditing = false
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.editForm = {
|
|
|
|
|
+ name: this.contact.name || '',
|
|
|
|
|
+ phone: this.contact.phone || '',
|
|
|
|
|
+ relationshipType: this.contact.relationshipType || '',
|
|
|
|
|
+ relationshipTypeLabel: this.reverseTypeValues[this.contact.relationshipType] || '其他',
|
|
|
|
|
+ knownSince: this.contact.knownSince ? this.contact.knownSince.substring(0, 10) : '',
|
|
|
|
|
+ birthday: this.contact.birthday ? this.contact.birthday.substring(0, 10) : '',
|
|
|
|
|
+ bio: this.contact.bio || '',
|
|
|
|
|
+ notes: this.contact.notes || ''
|
|
|
|
|
+ }
|
|
|
|
|
+ this.isEditing = true
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onTypeChange: function(e) {
|
|
|
|
|
+ var label = this.typeOptions[e.detail.value]
|
|
|
|
|
+ this.editForm.relationshipType = this.typeValues[label] || 'other'
|
|
|
|
|
+ this.editForm.relationshipTypeLabel = label
|
|
|
|
|
+ },
|
|
|
|
|
+ onDateChange: function(e) {
|
|
|
|
|
+ this.editForm.knownSince = e.detail.value
|
|
|
|
|
+ },
|
|
|
|
|
+ onBirthdayChange: function(e) {
|
|
|
|
|
+ this.editForm.birthday = e.detail.value
|
|
|
|
|
+ },
|
|
|
|
|
+ saveEdit: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ var payload = { id: this.contact.id, name: this.editForm.name, phone: this.editForm.phone }
|
|
|
|
|
+ if (this.editForm.relationshipType) payload.relationshipType = this.editForm.relationshipType
|
|
|
|
|
+ if (this.editForm.knownSince) payload.knownSince = this.editForm.knownSince
|
|
|
|
|
+ if (this.editForm.birthday) payload.birthday = this.editForm.birthday
|
|
|
|
|
+ if (this.editForm.bio !== undefined) payload.bio = this.editForm.bio
|
|
|
|
|
+ if (this.editForm.notes !== undefined) payload.notes = this.editForm.notes
|
|
|
|
|
+ updateContact(payload).then(function(res) {
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ uni.showToast({ title: '保存成功', icon: 'success' })
|
|
|
|
|
+ self.isEditing = false
|
|
|
|
|
+ self.loadDetail()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: res.message || '保存失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ markInteraction: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ recordContactInteraction(this.contact.id).then(function(res) {
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ uni.showToast({ title: '已标记联系', icon: 'success' })
|
|
|
|
|
+ self.loadDetail()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: res.message || '操作失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmDelete: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '确认删除',
|
|
|
|
|
+ content: '确定要删除该联系人吗?',
|
|
|
|
|
+ success: function(r) {
|
|
|
|
|
+ if (r.confirm) {
|
|
|
|
|
+ deleteContact({ id: self.contact.id }).then(function(res) {
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ uni.showToast({ title: '已删除', icon: 'success' })
|
|
|
|
|
+ setTimeout(function() { uni.navigateBack() }, 500)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: res.message || '删除失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ formatDate: function(dateStr) {
|
|
|
|
|
+ if (!dateStr) return '-'
|
|
|
|
|
+ return dateStr.substring(0, 10)
|
|
|
|
|
+ },
|
|
|
|
|
+ goBack: function() {
|
|
|
|
|
+ uni.navigateBack()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.detail-page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ padding-bottom: 40rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.detail-header {
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea, #764ba2);
|
|
|
|
|
+ padding: 60rpx 30rpx 40rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+.header-top {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-bottom: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.back-btn, .edit-btn {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ padding: 10rpx 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.page-title { font-size: 32rpx; font-weight: bold; }
|
|
|
|
|
+.header-avatar {
|
|
|
|
|
+ width: 120rpx;
|
|
|
|
|
+ height: 120rpx;
|
|
|
|
|
+ border-radius: 60rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+ border: 4rpx solid rgba(255,255,255,0.4);
|
|
|
|
|
+}
|
|
|
|
|
+.avatar-text { font-size: 48rpx; font-weight: bold; color: #fff; }
|
|
|
|
|
+.header-name { font-size: 36rpx; font-weight: bold; margin-bottom: 8rpx; }
|
|
|
|
|
+.header-meta { font-size: 26rpx; opacity: 0.9; margin-bottom: 4rpx; }
|
|
|
|
|
+.header-known { font-size: 24rpx; opacity: 0.7; margin-bottom: 12rpx; }
|
|
|
|
|
+.header-intimacy { display: flex; align-items: center; gap: 8rpx; }
|
|
|
|
|
+.intimacy-heart { font-size: 32rpx; }
|
|
|
|
|
+.intimacy-num { font-size: 40rpx; font-weight: bold; }
|
|
|
|
|
+.intimacy-label { font-size: 24rpx; opacity: 0.8; }
|
|
|
|
|
+
|
|
|
|
|
+.info-section, .interaction-section {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ margin: 20rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ padding: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.section-title {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.info-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.info-icon { font-size: 28rpx; margin-right: 12rpx; width: 40rpx; }
|
|
|
|
|
+.info-value { font-size: 26rpx; color: #555; flex: 1; }
|
|
|
|
|
+.countdown { color: #EC4899; }
|
|
|
|
|
+
|
|
|
|
|
+.edit-row {
|
|
|
|
|
+ margin-bottom: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-label {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-input {
|
|
|
|
|
+ border: 1rpx solid #ddd;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+.edit-picker {
|
|
|
|
|
+ border: 1rpx solid #ddd;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.btn-save {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ background: linear-gradient(135deg, #FF6B35, #F97316);
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.interaction-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ padding: 16rpx 0;
|
|
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
+}
|
|
|
|
|
+.interaction-date { font-size: 24rpx; color: #999; }
|
|
|
|
|
+.interaction-desc { font-size: 26rpx; color: #555; }
|
|
|
|
|
+.interaction-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
|
|
|
|
|
+.btn-interaction {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea, #764ba2);
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ margin-top: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.danger-section { margin: 20rpx; }
|
|
|
|
|
+.btn-delete {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1rpx solid #ff4d4f;
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ color: #ff4d4f;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|