643 lines
21 KiB
Vue
643 lines
21 KiB
Vue
<template>
|
|
<div class="member-management">
|
|
<el-card>
|
|
<template #header>
|
|
<div class="card-header">
|
|
<div class="search-section">
|
|
<el-input
|
|
v-model="searchKeyword"
|
|
placeholder="搜索会员编号/昵称/手机号"
|
|
clearable
|
|
style="width: 280px"
|
|
@clear="handleSearch"
|
|
@keyup.enter="handleSearch"
|
|
>
|
|
<template #prefix>
|
|
<el-icon><Search /></el-icon>
|
|
</template>
|
|
</el-input>
|
|
<el-button type="primary" @click="handleSearch">
|
|
搜索
|
|
</el-button>
|
|
</div>
|
|
<div class="sort-section">
|
|
<el-button @click="handleResetSort" :type="sortField === 'createdAt' && sortOrder === 'desc' ? 'primary' : 'default'">
|
|
<el-icon><Clock /></el-icon>
|
|
最近注册
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<el-table
|
|
v-loading="loading"
|
|
:data="dataSource"
|
|
style="width: 100%"
|
|
@sort-change="handleSortChange"
|
|
>
|
|
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
|
|
<el-table-column label="头像" width="80">
|
|
<template #default="{ row }">
|
|
<el-avatar v-if="row.avatar" :src="row.avatar" :size="40" />
|
|
<el-avatar v-else :size="40">
|
|
<el-icon><UserFilled /></el-icon>
|
|
</el-avatar>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="memberNo" label="会员编号" width="140" sortable="custom">
|
|
<template #default="{ row }">
|
|
{{ row.memberNo || '-' }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="nickname" label="昵称" min-width="120">
|
|
<template #default="{ row }">
|
|
{{ row.nickname || '-' }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="手机号" width="140">
|
|
<template #default="{ row }">
|
|
{{ formatPhone(row.phone) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="性别" width="70">
|
|
<template #default="{ row }">
|
|
{{ row.genderDesc || genderMap[row.gender as number] || '-' }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="注册时间" width="170" prop="createdAt" sortable="custom">
|
|
<template #default="{ row }">
|
|
{{ row.createdAt ? formatDateTime(row.createdAt) : '-' }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="240" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button type="primary" link size="small" @click="handleDetail(row)">
|
|
详情
|
|
</el-button>
|
|
<el-button type="primary" link size="small" @click="handleEdit(row)">
|
|
编辑
|
|
</el-button>
|
|
<el-button type="warning" link size="small" @click="handleEditPhone(row)">
|
|
修改手机号
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-pagination
|
|
v-model:current-page="pagination.current"
|
|
v-model:page-size="pagination.pageSize"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
:total="pagination.total"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
style="margin-top: 16px; justify-content: flex-end"
|
|
@current-change="handleTableChange"
|
|
@size-change="handleSizeChange"
|
|
/>
|
|
</el-card>
|
|
|
|
<!-- 详情弹窗 -->
|
|
<el-dialog
|
|
v-model="detailVisible"
|
|
title="会员详情"
|
|
width="650px"
|
|
>
|
|
<div v-loading="detailLoading" style="min-height: 200px">
|
|
<el-descriptions v-if="currentDetail" :column="2" border>
|
|
<el-descriptions-item label="ID">{{ currentDetail.id }}</el-descriptions-item>
|
|
<el-descriptions-item label="会员编号">{{ currentDetail.memberNo || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="昵称">{{ currentDetail.nickname || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="手机号">{{ formatPhone(currentDetail.phone) }}</el-descriptions-item>
|
|
<el-descriptions-item label="性别">{{ currentDetail.genderDesc || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="生日">{{ currentDetail.birthday || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="地址" :span="2">{{ currentDetail.address || '-' }}</el-descriptions-item>
|
|
<el-descriptions-item label="关注状态">
|
|
<el-tag :type="currentDetail.subscribed ? 'success' : 'info'" size="small">
|
|
{{ currentDetail.subscribed ? '已关注' : '未关注' }}
|
|
</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item label="最后登录">{{ formatDateTime(currentDetail.lastLoginAt) }}</el-descriptions-item>
|
|
<el-descriptions-item label="注册时间">{{ formatDateTime(currentDetail.createdAt) }}</el-descriptions-item>
|
|
<el-descriptions-item label="有效会员卡">{{ currentDetail.activeCardCount ?? 0 }}</el-descriptions-item>
|
|
<el-descriptions-item label="头像" :span="2">
|
|
<el-avatar v-if="currentDetail.avatar" :src="currentDetail.avatar" :size="60" />
|
|
<span v-else>未设置</span>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
|
|
<!-- 绑定的会员卡记录 -->
|
|
<div style="margin-top: 20px">
|
|
<h4 style="margin-bottom: 10px">
|
|
绑定的会员卡
|
|
<span v-if="currentDetail?.activeCardCount !== undefined" style="font-weight: normal; font-size: 13px; color: #909399; margin-left: 8px">
|
|
(有效 {{ currentDetail.activeCardCount }} 张 / 共 {{ (currentDetail.activeCardCount ?? 0) + (currentDetail.inactiveCardCount ?? 0) }} 张)
|
|
</span>
|
|
</h4>
|
|
<el-table v-if="memberCardRecords.length > 0" :data="memberCardRecords" size="small" border>
|
|
<el-table-column prop="memberCardName" label="卡名称" min-width="120" />
|
|
<el-table-column label="卡类型" width="90">
|
|
<template #default="{ row: rec }">
|
|
{{ cardTypeMap[rec.memberCardType as string] || rec.memberCardType || '-' }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="剩余" width="120">
|
|
<template #default="{ row: rec }">
|
|
<template v-if="rec.memberCardType === 'TIME_CARD'">
|
|
{{ formatDateTime(rec.expireTime) }}
|
|
</template>
|
|
<template v-else-if="rec.memberCardType === 'COUNT_CARD'">
|
|
{{ rec.remainingTimes ?? 0 }} 次
|
|
</template>
|
|
<template v-else-if="rec.memberCardType === 'STORED_VALUE_CARD'">
|
|
¥{{ rec.remainingAmount ?? 0 }}
|
|
</template>
|
|
<template v-else>-</template>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="购买时间" width="170">
|
|
<template #default="{ row: rec }">
|
|
{{ formatDateTime(rec.purchaseTime) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="status" label="状态" width="80">
|
|
<template #default="{ row: rec }">
|
|
<el-tag :type="rec.status === 'ACTIVE' ? 'success' : 'info'" size="small">
|
|
{{ rec.status || '-' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
<el-empty v-else description="暂无绑定的会员卡" :image-size="60" />
|
|
</div>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<!-- 编辑弹窗 -->
|
|
<el-dialog
|
|
v-model="editVisible"
|
|
title="编辑会员信息"
|
|
width="500px"
|
|
>
|
|
<div v-loading="editLoading" style="min-height: 200px">
|
|
<el-form
|
|
v-if="!editLoading"
|
|
ref="editFormRef"
|
|
:model="editForm"
|
|
label-width="100px"
|
|
>
|
|
<el-form-item label="昵称">
|
|
<el-input v-model="editForm.nickname" placeholder="请输入昵称" />
|
|
</el-form-item>
|
|
<el-form-item label="性别">
|
|
<el-select v-model="editForm.gender" placeholder="请选择性别" style="width: 100%">
|
|
<el-option value="MALE" label="男" />
|
|
<el-option value="FEMALE" label="女" />
|
|
<el-option value="UNKNOWN" label="未知" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="生日">
|
|
<el-date-picker
|
|
v-model="editForm.birthday"
|
|
type="date"
|
|
placeholder="选择生日"
|
|
value-format="YYYY-MM-DD"
|
|
style="width: 100%"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="地址">
|
|
<el-input v-model="editForm.address" placeholder="请输入地址" />
|
|
</el-form-item>
|
|
<el-form-item label="头像">
|
|
<div class="avatar-edit-row">
|
|
<div class="avatar-upload">
|
|
<el-upload
|
|
class="avatar-uploader"
|
|
:http-request="uploadAvatar"
|
|
:before-upload="beforeAvatarUpload"
|
|
:show-file-list="false"
|
|
accept="image/*"
|
|
>
|
|
<el-avatar v-if="editForm.avatar" :src="editForm.avatar" :size="80" />
|
|
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
|
|
</el-upload>
|
|
<span class="avatar-hint">点击上传</span>
|
|
</div>
|
|
<el-button
|
|
v-if="editForm.avatar"
|
|
type="danger"
|
|
plain
|
|
size="small"
|
|
@click="handleClearAvatar"
|
|
>
|
|
删除头像
|
|
</el-button>
|
|
</div>
|
|
</el-form-item>
|
|
</el-form>
|
|
</div>
|
|
<template #footer>
|
|
<el-button @click="editVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="handleEditSubmit" :loading="editSubmitting" :disabled="editLoading">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 修改手机号弹窗 -->
|
|
<el-dialog
|
|
v-model="phoneVisible"
|
|
title="修改手机号"
|
|
width="400px"
|
|
>
|
|
<el-form
|
|
ref="phoneFormRef"
|
|
:model="phoneForm"
|
|
:rules="phoneRules"
|
|
label-width="100px"
|
|
>
|
|
<el-form-item label="会员ID">
|
|
<span>{{ currentMemberId }}</span>
|
|
</el-form-item>
|
|
<el-form-item label="新手机号" prop="phone">
|
|
<el-input v-model="phoneForm.phone" placeholder="请输入新手机号" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="phoneVisible = false">取消</el-button>
|
|
<el-button type="primary" @click="handlePhoneSubmit">确定</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import { Search, UserFilled, Plus, Clock } from '@element-plus/icons-vue'
|
|
import { memberApi, type Member, type MemberDetail, type MemberCardRecord, type UpdateMemberDto } from '@/api/member.api'
|
|
import request from '@/utils/request'
|
|
import { formatDateTime } from '@/utils/dateFormat'
|
|
|
|
const loading = ref(false)
|
|
const dataSource = ref<Member[]>([])
|
|
const searchKeyword = ref('')
|
|
const sortField = ref<string>('')
|
|
const sortOrder = ref<string>('')
|
|
|
|
const genderMap: Record<number, string> = {
|
|
0: '未知',
|
|
1: '男',
|
|
2: '女',
|
|
}
|
|
|
|
const cardTypeMap: Record<string, string> = {
|
|
TIME_CARD: '时长卡',
|
|
COUNT_CARD: '次卡',
|
|
STORED_VALUE_CARD: '储值卡',
|
|
}
|
|
|
|
/** 格式化手机号显示,null/空时显示"未绑定" */
|
|
const formatPhone = (phone: string | undefined | null): string => {
|
|
if (!phone) return '未绑定'
|
|
return phone
|
|
}
|
|
|
|
const pagination = reactive({
|
|
current: 1,
|
|
pageSize: 10,
|
|
total: 0,
|
|
})
|
|
|
|
// 详情相关
|
|
const detailVisible = ref(false)
|
|
const detailLoading = ref(false)
|
|
const currentDetail = ref<MemberDetail | null>(null)
|
|
const memberCardRecords = ref<MemberCardRecord[]>([])
|
|
|
|
// 编辑相关
|
|
const editVisible = ref(false)
|
|
const editLoading = ref(false)
|
|
const editSubmitting = ref(false)
|
|
const editFormRef = ref()
|
|
const editingMemberId = ref<number>()
|
|
const editForm = reactive<UpdateMemberDto>({
|
|
nickname: '',
|
|
gender: undefined,
|
|
birthday: '',
|
|
address: '',
|
|
avatar: '',
|
|
})
|
|
|
|
// 修改手机号相关
|
|
const phoneVisible = ref(false)
|
|
const phoneFormRef = ref()
|
|
const currentMemberId = ref<number>()
|
|
const phoneForm = reactive({
|
|
phone: '',
|
|
})
|
|
const phoneRules = {
|
|
phone: [
|
|
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' },
|
|
],
|
|
}
|
|
|
|
const fetchData = async () => {
|
|
loading.value = true
|
|
try {
|
|
const params: any = {
|
|
pageNum: pagination.current,
|
|
pageSize: pagination.pageSize,
|
|
sortField: sortField.value || 'createdAt',
|
|
sortOrder: sortOrder.value || 'desc',
|
|
}
|
|
if (searchKeyword.value) {
|
|
params.searchValue = searchKeyword.value
|
|
}
|
|
let res: any
|
|
if (searchKeyword.value) {
|
|
res = await memberApi.search(params)
|
|
} else {
|
|
res = await memberApi.getAll(params)
|
|
}
|
|
console.log('会员列表API返回:', res)
|
|
|
|
// 兼容多种返回格式:数组、{content/records/list/data: [...]}
|
|
let list: any[] = []
|
|
if (Array.isArray(res)) {
|
|
list = res
|
|
} else if (res?.content) {
|
|
list = res.content
|
|
} else if (res?.records) {
|
|
list = res.records
|
|
} else if (res?.list) {
|
|
list = res.list
|
|
} else if (res?.data && Array.isArray(res.data)) {
|
|
list = res.data
|
|
}
|
|
dataSource.value = list
|
|
|
|
// 分页总数
|
|
if (res?.totalElements !== undefined) {
|
|
pagination.total = Number(res.totalElements)
|
|
} else if (res?.total !== undefined) {
|
|
pagination.total = Number(res.total)
|
|
} else if (Array.isArray(res)) {
|
|
pagination.total = res.length
|
|
} else {
|
|
pagination.total = list.length
|
|
}
|
|
} catch (e: any) {
|
|
const msg = e?.response?.data?.message || e?.message || '加载数据失败'
|
|
ElMessage.error(msg)
|
|
console.error('加载会员列表失败:', e)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const handleTableChange = () => fetchData()
|
|
const handleSizeChange = () => {
|
|
pagination.current = 1
|
|
fetchData()
|
|
}
|
|
const handleSearch = () => {
|
|
pagination.current = 1
|
|
fetchData()
|
|
}
|
|
|
|
const handleSortChange = ({ prop, order }: { prop: string; order: string | null }) => {
|
|
sortField.value = prop || ''
|
|
sortOrder.value = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : ''
|
|
pagination.current = 1
|
|
fetchData()
|
|
}
|
|
|
|
const handleResetSort = () => {
|
|
sortField.value = 'createdAt'
|
|
sortOrder.value = 'desc'
|
|
pagination.current = 1
|
|
fetchData()
|
|
}
|
|
|
|
const handleDetail = async (row: Member) => {
|
|
console.log('点击详情, row:', row, 'row.id:', row.id, 'type:', typeof row.id)
|
|
if (!row.id) return
|
|
detailVisible.value = true
|
|
detailLoading.value = true
|
|
currentDetail.value = null
|
|
memberCardRecords.value = []
|
|
try {
|
|
const detail = await memberApi.getDetail(Number(row.id))
|
|
console.log('会员详情API返回:', detail)
|
|
currentDetail.value = detail
|
|
// 获取绑定的会员卡记录
|
|
try {
|
|
const records: any = await memberApi.getMemberCardRecords(Number(row.id))
|
|
memberCardRecords.value = Array.isArray(records) ? records : (records?.content || records?.data || [])
|
|
} catch {
|
|
memberCardRecords.value = []
|
|
}
|
|
} catch (e: any) {
|
|
const msg = e?.response?.data?.message || e?.message || '获取会员详情失败'
|
|
ElMessage.error(msg)
|
|
console.error('获取会员详情失败:', e)
|
|
detailVisible.value = false
|
|
} finally {
|
|
detailLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleEdit = async (row: Member) => {
|
|
console.log('点击编辑, row:', row)
|
|
if (!row.id) return
|
|
editingMemberId.value = Number(row.id)
|
|
editVisible.value = true
|
|
editLoading.value = true
|
|
// 先重置表单
|
|
editForm.nickname = ''
|
|
editForm.gender = undefined
|
|
editForm.birthday = ''
|
|
editForm.address = ''
|
|
editForm.avatar = ''
|
|
try {
|
|
const detail = await memberApi.getDetail(Number(row.id))
|
|
console.log('编辑-会员详情API返回:', detail)
|
|
editForm.nickname = detail.nickname || ''
|
|
// genderDesc 为中文"男"/"女",需转为后端枚举值
|
|
if (detail.genderDesc === '男') {
|
|
editForm.gender = 'MALE'
|
|
} else if (detail.genderDesc === '女') {
|
|
editForm.gender = 'FEMALE'
|
|
} else {
|
|
editForm.gender = 'UNKNOWN'
|
|
}
|
|
// birthday 可能是 ISO 格式("2026-01-01T00:00:00.000+00:00"),取前10位
|
|
if (detail.birthday) {
|
|
const bd = String(detail.birthday)
|
|
editForm.birthday = bd.length >= 10 ? bd.substring(0, 10) : bd
|
|
} else {
|
|
editForm.birthday = ''
|
|
}
|
|
editForm.address = detail.address || ''
|
|
editForm.avatar = detail.avatar || ''
|
|
console.log('编辑表单数据:', JSON.parse(JSON.stringify(editForm)))
|
|
} catch (e: any) {
|
|
const msg = e?.response?.data?.message || e?.message || '获取会员信息失败'
|
|
ElMessage.error(msg)
|
|
console.error('获取会员信息失败:', e)
|
|
editVisible.value = false
|
|
} finally {
|
|
editLoading.value = false
|
|
}
|
|
}
|
|
|
|
const handleEditSubmit = async () => {
|
|
if (!editingMemberId.value) return
|
|
editSubmitting.value = true
|
|
try {
|
|
const body: UpdateMemberDto = {
|
|
nickname: editForm.nickname || undefined,
|
|
gender: editForm.gender || undefined,
|
|
birthday: editForm.birthday || undefined,
|
|
address: editForm.address || undefined,
|
|
avatar: editForm.avatar || undefined,
|
|
}
|
|
await memberApi.update(editingMemberId.value, body)
|
|
ElMessage.success('更新成功')
|
|
editVisible.value = false
|
|
fetchData()
|
|
} catch (e: any) {
|
|
const msg = e?.response?.data?.message || e?.message || '更新失败'
|
|
ElMessage.error(msg)
|
|
} finally {
|
|
editSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
// ========== 头像上传 ==========
|
|
|
|
const beforeAvatarUpload = (file: File) => {
|
|
const isImage = file.type.startsWith('image/')
|
|
if (!isImage) {
|
|
ElMessage.error('只能上传图片文件')
|
|
return false
|
|
}
|
|
const isLt5M = file.size / 1024 / 1024 < 5
|
|
if (!isLt5M) {
|
|
ElMessage.error('图片大小不能超过 5MB')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
const uploadAvatar = async (options: any) => {
|
|
const formData = new FormData()
|
|
formData.append('file', options.file)
|
|
try {
|
|
const res: any = await request.post('/files/upload', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
})
|
|
if (res && res.id) {
|
|
// 通过预览接口获取图片URL
|
|
const blob: any = await request.get(`/files/${res.id}/preview`, { responseType: 'blob' })
|
|
editForm.avatar = URL.createObjectURL(blob)
|
|
}
|
|
ElMessage.success('头像上传成功')
|
|
} catch {
|
|
ElMessage.error('头像上传失败')
|
|
}
|
|
}
|
|
|
|
const handleClearAvatar = () => {
|
|
editForm.avatar = ''
|
|
ElMessage.success('头像已清除,保存后生效')
|
|
}
|
|
|
|
// ========== 修改手机号 ==========
|
|
|
|
const handleEditPhone = (row: Member) => {
|
|
currentMemberId.value = Number(row.id)
|
|
phoneForm.phone = ''
|
|
phoneVisible.value = true
|
|
}
|
|
|
|
const handlePhoneSubmit = async () => {
|
|
if (!phoneFormRef.value || !currentMemberId.value) return
|
|
try {
|
|
await phoneFormRef.value.validate()
|
|
await memberApi.updatePhone(currentMemberId.value, { phone: phoneForm.phone })
|
|
ElMessage.success('手机号更新成功')
|
|
phoneVisible.value = false
|
|
fetchData()
|
|
} catch (error: any) {
|
|
if (error !== 'cancel' && error?.message !== 'cancel') {
|
|
const msg = error?.response?.data?.message || error?.message || '手机号更新失败'
|
|
ElMessage.error(msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
fetchData()
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="css">
|
|
.member-management {
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 16px;
|
|
|
|
.search-section {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.sort-section {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
.avatar-edit-row {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
gap: 16px;
|
|
}
|
|
|
|
.avatar-upload {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.avatar-uploader :deep(.el-upload) {
|
|
border: 1px dashed #d9d9d9;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
width: 80px;
|
|
height: 80px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: border-color 0.3s;
|
|
}
|
|
|
|
.avatar-uploader :deep(.el-upload:hover) {
|
|
border-color: #409eff;
|
|
}
|
|
|
|
.avatar-uploader-icon {
|
|
font-size: 28px;
|
|
color: #8c939d;
|
|
}
|
|
|
|
.avatar-hint {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
}
|
|
</style>
|