| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- <template>
- <view class="page-register" :class="themeClass">
- <view class="header">
- <text class="title">完善个人资料</text>
- <text class="subtitle">填写以下信息以开启完整体验</text>
- </view>
- <view class="form">
- <!-- Avatar -->
- <view class="form-item avatar-item" @click="chooseAvatar">
- <image class="avatar" :src="form.avatarUrl || '/static/default-avatar.png'" mode="aspectFill" />
- <text class="avatar-hint">点击更换头像</text>
- </view>
- <!-- Nickname -->
- <view class="form-item">
- <text class="label">昵称</text>
- <input class="input" v-model="form.nickname" placeholder="输入昵称" maxlength="20" />
- </view>
- <!-- Gender -->
- <view class="form-item">
- <text class="label">性别</text>
- <view class="radio-group">
- <text class="radio" :class="{ active: form.gender === 1 }" @click="form.gender = 1">男</text>
- <text class="radio" :class="{ active: form.gender === 2 }" @click="form.gender = 2">女</text>
- </view>
- </view>
- <!-- Birth date -->
- <view class="form-item">
- <text class="label">出生日期</text>
- <picker mode="date" :value="form.birthDate" @change="onBirthDateChange" class="picker">
- <text class="picker-text" :class="{ muted: !form.birthDate }">
- {{ form.birthDate || '请选择出生日期' }}
- </text>
- </picker>
- </view>
- <!-- Bio -->
- <view class="form-item">
- <text class="label">个人简介</text>
- <textarea class="textarea" v-model="form.bio" placeholder="简单介绍一下自己(选填)" maxlength="100" />
- <text class="char-counter">{{ form.bio.length }}/100</text>
- </view>
- <!-- City -->
- <view class="form-item">
- <text class="label">所在城市</text>
- <input class="input" v-model="form.city" placeholder="输入城市(选填)" maxlength="50" />
- </view>
- <!-- Interest tags -->
- <view class="form-item">
- <text class="label">兴趣标签</text>
- <view class="tag-grid">
- <text
- v-for="tag in interestOptions"
- :key="tag"
- class="tag-item"
- :class="{ active: selectedTags.includes(tag) }"
- @click="toggleTag(tag)"
- >{{ tag }}</text>
- </view>
- </view>
- <!-- Looking for -->
- <view class="form-item">
- <text class="label">想认识</text>
- <view class="radio-group">
- <text
- v-for="opt in lookingOptions"
- :key="opt.value"
- class="radio"
- :class="{ active: form.lookingFor === opt.value }"
- @click="form.lookingFor = opt.value"
- >{{ opt.label }}</text>
- </view>
- </view>
- <!-- Submit -->
- <button class="submit-btn" @click="handleSubmit" :disabled="submitting">
- {{ submitting ? '提交中...' : '提交' }}
- </button>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, reactive, computed } from 'vue'
- import { useUserStore } from '@/stores/user'
- import { profileApi } from '@/utils/api'
- import { useTheme } from '@/composables/useThemeStyle'
- const { themeClass } = useTheme()
- const userStore = useUserStore()
- const submitting = ref(false)
- const interestOptions = ['读书', '运动', '音乐', '旅行', '禅修', '创业', '心理学', '玄学']
- const lookingOptions = [
- { value: 0, label: '不限' },
- { value: 1, label: '朋友' },
- { value: 2, label: '导师' },
- { value: 3, label: '同修' }
- ]
- const form = reactive({
- avatarUrl: userStore.avatarUrl || '',
- nickname: userStore.nickname || '',
- gender: 0,
- birthDate: '',
- bio: '',
- city: '',
- tags: '',
- lookingFor: 0
- })
- const selectedTags = ref([])
- function toggleTag(tag) {
- const idx = selectedTags.value.indexOf(tag)
- if (idx >= 0) {
- selectedTags.value.splice(idx, 1)
- } else {
- selectedTags.value.push(tag)
- }
- form.tags = selectedTags.value.join(',')
- }
- function onBirthDateChange(e) {
- form.birthDate = e.detail.value
- }
- function chooseAvatar() {
- uni.chooseImage({
- count: 1,
- success: (res) => {
- const tempPath = res.tempFilePaths[0]
- // For now just use the temp path; real upload to server would happen via uni.uploadFile
- form.avatarUrl = tempPath
- // In production, upload to server and set form.avatarUrl to returned URL
- uploadAvatar(tempPath)
- }
- })
- }
- async function uploadAvatar(tempPath) {
- // Upload avatar to server (simplified - just uses temp path for now)
- // In production: uni.uploadFile to /api/upload/avatar
- uni.showToast({ title: '头像已选择', icon: 'success' })
- }
- async function handleSubmit() {
- if (!form.nickname.trim()) {
- uni.showToast({ title: '请输入昵称', icon: 'none' })
- return
- }
- if (form.gender === 0) {
- uni.showToast({ title: '请选择性别', icon: 'none' })
- return
- }
- if (!form.birthDate) {
- uni.showToast({ title: '请选择出生日期', icon: 'none' })
- return
- }
- submitting.value = true
- try {
- await profileApi.complete({
- nickname: form.nickname.trim(),
- avatarUrl: form.avatarUrl,
- gender: form.gender,
- birthDate: form.birthDate,
- bio: form.bio.trim(),
- city: form.city.trim(),
- tags: form.tags,
- lookingFor: form.lookingFor
- })
- // Refresh profile in store
- await userStore.fetchProfile()
- userStore.profileIncomplete = false
- uni.showToast({ title: '资料已完善', icon: 'success' })
- setTimeout(() => {
- uni.switchTab({ url: '/pages/index/index' })
- }, 500)
- } catch (e) {
- uni.showToast({ title: e.message || '提交失败', icon: 'none' })
- } finally {
- submitting.value = false
- }
- }
- </script>
- <style scoped lang="scss">
- .page-register {
- min-height: 100vh;
- background: linear-gradient(180deg, #F8F9FF 0%, #EEF2FF 100%);
- padding: 20px 24px 40px;
- }
- .header {
- text-align: center;
- padding: 24px 0 32px;
- }
- .title {
- font-size: 22px;
- font-weight: 700;
- color: #1F2937;
- display: block;
- }
- .subtitle {
- font-size: 14px;
- color: #9CA3AF;
- display: block;
- margin-top: 6px;
- }
- .form {
- background: #fff;
- border-radius: 16px;
- padding: 24px 20px;
- box-shadow: 0 2px 12px rgba(0,0,0,0.04);
- }
- .form-item {
- margin-bottom: 20px;
- }
- .label {
- font-size: 14px;
- font-weight: 600;
- color: #374151;
- display: block;
- margin-bottom: 8px;
- }
- .input {
- width: 100%;
- height: 44px;
- border: 1px solid #E5E7EB;
- border-radius: 10px;
- padding: 0 14px;
- font-size: 15px;
- color: #1F2937;
- background: #F9FAFB;
- box-sizing: border-box;
- }
- .textarea {
- width: 100%;
- height: 88px;
- border: 1px solid #E5E7EB;
- border-radius: 10px;
- padding: 10px 14px;
- font-size: 15px;
- color: #1F2937;
- background: #F9FAFB;
- box-sizing: border-box;
- resize: none;
- }
- .char-counter {
- display: block;
- text-align: right;
- font-size: 12px;
- color: #9CA3AF;
- margin-top: 4px;
- }
- /* Avatar */
- .avatar-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-bottom: 28px;
- }
- .avatar {
- width: 80px;
- height: 80px;
- border-radius: 50%;
- border: 3px solid #E5E7EB;
- }
- .avatar-hint {
- font-size: 12px;
- color: #9CA3AF;
- margin-top: 6px;
- }
- /* Radio group */
- .radio-group {
- display: flex;
- gap: 12px;
- }
- .radio {
- flex: 1;
- height: 44px;
- border: 1px solid #E5E7EB;
- border-radius: 10px;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 15px;
- color: #6B7280;
- background: #F9FAFB;
- }
- .radio.active {
- border-color: #4A148C;
- color: #4A148C;
- background: #F3EEFF;
- }
- /* Picker */
- .picker {
- width: 100%;
- }
- .picker-text {
- display: block;
- width: 100%;
- height: 44px;
- line-height: 44px;
- border: 1px solid #E5E7EB;
- border-radius: 10px;
- padding: 0 14px;
- font-size: 15px;
- color: #1F2937;
- background: #F9FAFB;
- box-sizing: border-box;
- }
- .picker-text.muted {
- color: #9CA3AF;
- }
- /* Tags */
- .tag-grid {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- .tag-item {
- padding: 6px 16px;
- border-radius: 20px;
- font-size: 13px;
- color: #6B7280;
- background: var(--color-bg-input);
- border: 1px solid transparent;
- }
- .tag-item.active {
- color: #4A148C;
- background: #F3EEFF;
- border-color: #4A148C;
- }
- /* Submit */
- .submit-btn {
- width: 100%;
- height: 48px;
- background: linear-gradient(135deg, #4A148C, #7B2DC8);
- color: #fff;
- border: none;
- border-radius: 12px;
- font-size: 17px;
- font-weight: 600;
- margin-top: 8px;
- }
- .submit-btn[disabled] {
- opacity: 0.5;
- }
- </style>
|