Files
yuxingu-Miniprogram-dev/pages/yxg/profile.vue
T
jackyu66gitandCursor b09d82df8d feat: 小程序统一走 Go 后台,咨询与登录切到 /api/v1
去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 00:26:28 +08:00

239 lines
7.5 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>
<yxg-page title="个人档案">
<text v-if="loading" class="hint">加载中</text>
<text v-else-if="error" class="err" @click="load">{{ error }} · 重试</text>
<template v-else>
<view class="card">
<text class="card-t">自己</text>
<view v-if="selfProfile" class="who">
<text class="who-n">{{ selfProfile.display_name || '我' }}</text>
<text class="who-m">生日 {{ selfProfile.birth_date || '未填写' }}</text>
<text class="who-m">{{ selfProfile.birth_place || '出生地未填' }}</text>
<view class="btn ghost" @click="startEdit(selfProfile)"><text>编辑</text></view>
</view>
<view v-else class="empty">
<text>还没有自己的档案先补一个生日吧</text>
</view>
</view>
<view class="card">
<view class="row-head">
<text class="card-t">重要的人</text>
<text class="add" @click="showAdd = !showAdd">{{ showAdd ? '取消' : '+ 添加' }}</text>
</view>
<view v-if="showAdd" class="form">
<input class="inp" v-model="newName" placeholder="称呼,如伴侣 / 朋友" />
<view class="date-row">
<input class="inp sm" type="number" v-model="newY" placeholder="年" />
<input class="inp sm" type="number" v-model="newM" placeholder="月" />
<input class="inp sm" type="number" v-model="newD" placeholder="日" />
</view>
<input class="inp" v-model="newPlace" placeholder="出生地(可选)" />
<text v-if="addError" class="err">{{ addError }}</text>
<view class="btn" @click="addOther"><text>{{ adding ? '保存中…' : '保存' }}</text></view>
</view>
<view v-for="p in others" :key="p.id" class="who">
<text class="who-n">{{ p.display_name || 'TA' }}</text>
<text class="who-m">{{ p.birth_date }} · {{ p.birth_place || '出生地未填' }}</text>
<view class="acts">
<text class="link" @click="startEdit(p)">编辑</text>
<text class="link danger" @click="remove(p)">删除</text>
</view>
</view>
</view>
<view v-if="editing" class="card">
<text class="card-t">编辑档案</text>
<input class="inp" v-model="formName" placeholder="称呼" />
<view class="date-row">
<input class="inp sm" type="number" v-model="formY" placeholder="年" />
<input class="inp sm" type="number" v-model="formM" placeholder="月" />
<input class="inp sm" type="number" v-model="formD" placeholder="日" />
</view>
<input class="inp" v-model="formPlace" placeholder="出生地" />
<text v-if="formError" class="err">{{ formError }}</text>
<view class="acts">
<view class="btn" @click="saveEdit"><text>{{ saving ? '保存中…' : '保存' }}</text></view>
<view class="btn ghost" @click="editing = null"><text>取消</text></view>
</view>
</view>
</template>
</yxg-page>
</template>
<script setup>
import YxgPage from '@/components/yxg/yxg-page.vue'
import { yxgApi } from '@/util/yxgApi.js'
import { ensureAccount, ensureOtherProfile, ensureSelfProfile } from '@/util/yxgAuth.js'
const loading = ref(true)
const error = ref('')
const items = ref([])
const showAdd = ref(false)
const newName = ref('')
const newY = ref('')
const newM = ref('')
const newD = ref('')
const newPlace = ref('')
const addError = ref('')
const adding = ref(false)
const editing = ref(null)
const formName = ref('')
const formY = ref('')
const formM = ref('')
const formD = ref('')
const formPlace = ref('')
const formError = ref('')
const saving = ref(false)
const selfProfile = computed(() => items.value.find((p) => p.relation === 'self'))
const others = computed(() => items.value.filter((p) => p.relation !== 'self'))
function ymd(y, m, d) {
const Y = Number(y), M = Number(m), D = Number(d)
if (!Y || !M || !D || M < 1 || M > 12 || D < 1 || D > 31) return ''
return `${Y}-${String(M).padStart(2, '0')}-${String(D).padStart(2, '0')}`
}
async function load() {
loading.value = true
error.value = ''
if (!(await ensureAccount('/profile'))) {
loading.value = false
return
}
try {
const res = await yxgApi.listProfiles()
items.value = res.items || []
} catch (e) {
error.value = e instanceof Error ? e.message : '加载失败'
} finally {
loading.value = false
}
}
function startEdit(p) {
editing.value = p
formName.value = p.display_name || ''
const [y, m, d] = String(p.birth_date || '').split('-')
formY.value = y || ''
formM.value = m || ''
formD.value = d || ''
formPlace.value = p.birth_place || ''
formError.value = ''
}
async function saveEdit() {
if (!editing.value) return
const birth = ymd(formY.value, formM.value, formD.value)
if (!birth) {
formError.value = '请填写完整生日'
return
}
saving.value = true
formError.value = ''
try {
if (editing.value.relation === 'self') {
await ensureSelfProfile({
birth_date: birth,
display_name: formName.value,
birth_place: formPlace.value,
})
} else {
await yxgApi.updateProfile(editing.value.id, {
display_name: formName.value.trim() || 'TA',
birth_date: birth,
birth_place: formPlace.value,
})
}
editing.value = null
await load()
} catch (e) {
formError.value = e instanceof Error ? e.message : '保存失败'
} finally {
saving.value = false
}
}
async function addOther() {
const birth = ymd(newY.value, newM.value, newD.value)
if (!birth) {
addError.value = '请填写完整生日'
return
}
adding.value = true
addError.value = ''
try {
await ensureOtherProfile({
birth_date: birth,
display_name: newName.value || 'TA',
birth_place: newPlace.value,
})
showAdd.value = false
newName.value = newY.value = newM.value = newD.value = newPlace.value = ''
await load()
} catch (e) {
addError.value = e instanceof Error ? e.message : '添加失败'
} finally {
adding.value = false
}
}
function remove(p) {
uni.showModal({
title: '删除档案',
content: `确定删除「${p.display_name || 'TA'}」?`,
success: async (res) => {
if (!res.confirm) return
try {
await yxgApi.deleteProfile(p.id)
await load()
} catch (e) {
uni.showToast({ title: e.message || '删除失败', icon: 'none' })
}
},
})
}
onLoad((q) => {
if (q.add === '1') showAdd.value = true
load()
})
</script>
<style>
page { background-color: #ffd1c7; }
</style>
<style scoped>
.hint, .err { display: block; padding: 16px; font-size: 13px; color: #999; }
.err { color: #e54d42; }
.card {
margin: 12px 16px 0; padding: 16px; background: #fff; border-radius: 18px;
box-shadow: 0 2px 10px rgba(0,0,0,.04);
}
.card-t { display: block; font-size: 16px; font-weight: 700; color: #333; margin-bottom: 10px; }
.row-head { display: flex; justify-content: space-between; align-items: center; }
.add { font-size: 13px; color: #e54d42; }
.who { padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
.who:last-child { border-bottom: none; }
.who-n { display: block; font-size: 15px; font-weight: 650; color: #222; }
.who-m { display: block; font-size: 12px; color: #999; margin-top: 2px; }
.empty { font-size: 13px; color: #999; }
.form { margin-top: 8px; }
.inp {
width: 100%; height: 42px; padding: 0 12px; box-sizing: border-box;
background: #fdfaf8; border-radius: 12px; font-size: 14px; margin-bottom: 8px;
}
.date-row { display: flex; gap: 8px; }
.inp.sm { flex: 1; }
.btn {
height: 40px; border-radius: 999px; background: #e54d42; color: #fff;
display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 700;
margin-top: 6px;
}
.btn.ghost { background: #f7f2ef; color: #8a4a3a; }
.acts { display: flex; gap: 12px; margin-top: 6px; }
.link { font-size: 13px; color: #4a90e2; }
.link.danger { color: #e54d42; }
</style>