去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。 Co-authored-by: Cursor <cursoragent@cursor.com>
239 lines
7.5 KiB
Vue
239 lines
7.5 KiB
Vue
<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>
|