Files
pc-10584/app/pages/console/account/index.vue
赵忠林 775841eed3 feat(core): 初始化项目基础架构和CMS功能模块
- 添加Docker相关配置文件(.dockerignore, .env.example, .gitignore)
- 实现服务端API代理功能,支持文件、模块和服务器API转发
- 创建文章详情页、栏目文章列表页和单页内容展示页面
- 集成Ant Design Vue组件库并实现SSR样式提取功能
- 定义API响应数据结构类型和应用布局组件
- 开发开发者应用中心和文章管理页面
- 实现CMS导航菜单获取和多租户切换功能
2026-01-27 00:14:08 +08:00

348 lines
12 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>
<div class="space-y-4">
<a-page-header title="账号信息" sub-title="基本资料与企业信息" />
<a-spin :spinning="loading" tip="加载中...">
<a-row :gutter="[16, 16]">
<a-col :xs="24" :lg="12">
<a-card :bordered="false" class="card" title="基本资料">
<div class="flex items-center gap-4">
<a-avatar :size="56" :src="avatarUrl">
<template v-if="!avatarUrl" #icon>
<UserOutlined />
</template>
</a-avatar>
<div class="min-w-0">
<div class="text-base font-semibold text-gray-900">
{{ user?.nickname || user?.username || '未命名用户' }}
</div>
<div class="text-gray-500">
{{ user?.phone || (user as any)?.mobile || user?.email || '' }}
</div>
</div>
</div>
<a-divider />
<a-descriptions :column="1" size="small" bordered>
<a-descriptions-item label="用户ID">{{ user?.userId ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="账号">{{ user?.username ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="昵称">{{ user?.nickname ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="手机号">{{ user?.phone || (user as any)?.mobile || '-' }}</a-descriptions-item>
<a-descriptions-item label="邮箱">{{ user?.email ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="租户ID">{{ user?.tenantId ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="租户名称">{{ user?.tenantName ?? '-' }}</a-descriptions-item>
</a-descriptions>
<div class="mt-6 flex justify-end gap-2">
<a-button @click="reload" :loading="loading">刷新</a-button>
<a-button type="primary" :disabled="!user" @click="openEditUser">编辑</a-button>
</div>
</a-card>
</a-col>
<a-col :xs="24" :lg="12">
<a-card :bordered="false" class="card" title="企业信息">
<a-descriptions :column="1" size="small" bordered>
<a-descriptions-item label="企业ID">{{ company?.companyId ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="企业简称">{{ company?.shortName ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="企业全称">{{ company?.companyName ?? company?.tenantName ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="绑定域名">{{ company?.domain ?? company?.freeDomain ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="联系电话">{{ company?.phone ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="邮箱">{{ company?.email ?? '-' }}</a-descriptions-item>
<a-descriptions-item label="地址">
{{ companyAddress || '-' }}
</a-descriptions-item>
<a-descriptions-item label="实名认证">
<a-tag v-if="company?.authentication" color="green">已认证</a-tag>
<a-tag v-else color="default">未认证</a-tag>
</a-descriptions-item>
</a-descriptions>
<div class="mt-6 flex justify-end gap-2">
<a-button @click="reload" :loading="loading">刷新</a-button>
<a-button type="primary" :disabled="!company" @click="openEditCompany">编辑</a-button>
</div>
</a-card>
</a-col>
</a-row>
</a-spin>
<a-modal
v-model:open="editUserOpen"
title="编辑基本资料"
:confirm-loading="savingUser"
ok-text="保存"
cancel-text="取消"
@ok="submitUser"
>
<a-form ref="userFormRef" layout="vertical" :model="userForm" :rules="userRules">
<a-form-item label="昵称" name="nickname">
<a-input v-model:value="userForm.nickname" placeholder="请输入昵称" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="userForm.email" placeholder="例如name@example.com" />
</a-form-item>
<a-form-item label="头像 URL" name="avatarUrl">
<a-input v-model:value="userForm.avatarUrl" placeholder="https://..." />
</a-form-item>
</a-form>
</a-modal>
<a-modal
v-model:open="editCompanyOpen"
title="编辑企业信息"
:confirm-loading="savingCompany"
ok-text="保存"
cancel-text="取消"
@ok="submitCompany"
>
<a-form ref="companyFormRef" layout="vertical" :model="companyForm" :rules="companyRules">
<a-form-item label="企业简称" name="shortName">
<a-input v-model:value="companyForm.shortName" placeholder="例如:某某科技" />
</a-form-item>
<a-form-item label="企业全称" name="companyName">
<a-input v-model:value="companyForm.companyName" placeholder="例如:某某科技有限公司" />
</a-form-item>
<a-form-item label="绑定域名" name="domain">
<a-input v-model:value="companyForm.domain" placeholder="例如example.com" />
</a-form-item>
<a-form-item label="联系电话" name="phone">
<a-input v-model:value="companyForm.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="邮箱" name="email">
<a-input v-model:value="companyForm.email" placeholder="例如service@example.com" />
</a-form-item>
<a-form-item label="地址" name="address">
<a-textarea v-model:value="companyForm.address" :auto-size="{ minRows: 2, maxRows: 4 }" />
</a-form-item>
<a-form-item label="发票抬头" name="invoiceHeader">
<a-input v-model:value="companyForm.invoiceHeader" placeholder="用于开票" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { message, type FormInstance } from 'ant-design-vue'
import { UserOutlined } from '@ant-design/icons-vue'
import { getTenantInfo, getUserInfo, updateLoginUser } from '@/api/layout'
import { updateCompany } from '@/api/system/company'
import type { Company } from '@/api/system/company/model'
import type { User } from '@/api/system/user/model'
definePageMeta({ layout: 'console' })
const loading = ref(false)
const savingUser = ref(false)
const savingCompany = ref(false)
const user = ref<User | null>(null)
const company = ref<Company | null>(null)
const avatarUrl = computed(() => {
const candidate =
user.value?.avatarUrl ||
user.value?.avatar ||
user.value?.merchantAvatar ||
user.value?.logo ||
''
if (typeof candidate !== 'string') return ''
const normalized = candidate.trim()
if (!normalized || normalized === 'null' || normalized === 'undefined') return ''
return normalized
})
const companyAddress = computed(() => {
const parts = [company.value?.province, company.value?.city, company.value?.region, company.value?.address]
.map((v) => (typeof v === 'string' ? v.trim() : ''))
.filter(Boolean)
return parts.join(' ')
})
async function load() {
loading.value = true
try {
const [uRes, cRes] = await Promise.allSettled([getUserInfo(), getTenantInfo()])
if (uRes.status === 'fulfilled') {
user.value = uRes.value
} else {
console.error(uRes.reason)
message.error(uRes.reason instanceof Error ? uRes.reason.message : '获取用户信息失败')
}
if (cRes.status === 'fulfilled') {
company.value = cRes.value
} else {
console.error(cRes.reason)
message.error(cRes.reason instanceof Error ? cRes.reason.message : '获取企业信息失败')
}
} finally {
loading.value = false
}
}
async function reload() {
await load()
}
const editUserOpen = ref(false)
const userFormRef = ref<FormInstance>()
const userForm = reactive<{ nickname?: string; email?: string; avatarUrl?: string }>({
nickname: '',
email: '',
avatarUrl: ''
})
const userRules = reactive({
nickname: [{ required: true, message: '请输入昵称', type: 'string' }],
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }]
})
function openEditUser() {
if (!user.value) return
userForm.nickname = user.value.nickname ?? ''
userForm.email = user.value.email ?? ''
userForm.avatarUrl = user.value.avatarUrl ?? avatarUrl.value ?? ''
editUserOpen.value = true
}
async function submitUser() {
if (!user.value) return
try {
await userFormRef.value?.validate()
} catch {
return
}
const nickname = (userForm.nickname ?? '').trim()
if (!nickname) {
message.error('请输入昵称')
return
}
const email = (userForm.email ?? '').trim()
const avatar = (userForm.avatarUrl ?? '').trim()
savingUser.value = true
try {
await updateLoginUser({
userId: user.value.userId,
nickname,
email: email || undefined,
avatarUrl: avatar || undefined
} as User)
message.success('保存成功')
editUserOpen.value = false
await load()
} catch (e: unknown) {
console.error(e)
message.error(e instanceof Error ? e.message : '保存失败')
} finally {
savingUser.value = false
}
}
const editCompanyOpen = ref(false)
const companyFormRef = ref<FormInstance>()
const companyForm = reactive<{
companyId?: number
shortName?: string
companyName?: string
domain?: string
phone?: string
email?: string
address?: string
invoiceHeader?: string
}>({
companyId: undefined,
shortName: '',
companyName: '',
domain: '',
phone: '',
email: '',
address: '',
invoiceHeader: ''
})
const companyRules = reactive({
companyName: [{ required: true, message: '请输入企业全称', type: 'string' }],
email: [{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],
phone: [
{
validator: (_rule: unknown, value: unknown) => {
const normalized = typeof value === 'string' ? value.trim() : ''
if (!normalized) return Promise.resolve()
const mobileReg = /^1[3-9]\d{9}$/
if (mobileReg.test(normalized)) return Promise.resolve()
return Promise.reject(new Error('手机号格式不正确'))
},
trigger: 'blur'
}
]
})
function openEditCompany() {
if (!company.value) return
companyForm.companyId = company.value.companyId
companyForm.shortName = company.value.shortName ?? ''
companyForm.companyName = company.value.companyName ?? company.value.tenantName ?? ''
companyForm.domain = company.value.domain ?? ''
companyForm.phone = company.value.phone ?? ''
companyForm.email = company.value.email ?? ''
companyForm.address = company.value.address ?? ''
companyForm.invoiceHeader = company.value.invoiceHeader ?? ''
editCompanyOpen.value = true
}
async function submitCompany() {
if (!company.value) return
try {
await companyFormRef.value?.validate()
} catch {
return
}
if (!companyForm.companyId) {
message.error('企业ID缺失无法保存')
return
}
const companyName = (companyForm.companyName ?? '').trim()
if (!companyName) {
message.error('请输入企业全称')
return
}
savingCompany.value = true
try {
const domain = (companyForm.domain ?? '').trim()
const phone = (companyForm.phone ?? '').trim()
const email = (companyForm.email ?? '').trim()
const address = (companyForm.address ?? '').trim()
const invoiceHeader = (companyForm.invoiceHeader ?? '').trim()
await updateCompany({
companyId: companyForm.companyId,
shortName: (companyForm.shortName ?? '').trim() || undefined,
companyName,
domain: domain || undefined,
phone: phone || undefined,
email: email || undefined,
address: address || undefined,
invoiceHeader: invoiceHeader || undefined
} as Company)
message.success('保存成功')
editCompanyOpen.value = false
await load()
} catch (e: unknown) {
console.error(e)
message.error(e instanceof Error ? e.message : '保存失败')
} finally {
savingCompany.value = false
}
}
onMounted(load)
</script>
<style scoped>
.card {
border-radius: 12px;
}
</style>