feat: 完成客户端联调与通用列表组件

This commit is contained in:
2026-08-01 12:17:33 +08:00
parent be7675954a
commit beb86d53e7
31 changed files with 1326 additions and 378 deletions
+58 -23
View File
@@ -1,8 +1,9 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { UploadFile, UploadInstance } from 'element-plus'
import type { FormInstance, FormRules, UploadFile, UploadInstance } from 'element-plus'
import { Building2, CheckCircle2, Clock3, FileCheck2, FileText, Pencil, Trash2, Upload, UserRound } from '@lucide/vue'
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
import { useTenantAdministrationStore, type TenantCompanyProfile, type TenantQualification } from '@/stores/tenantAdministration'
import { useAuthStore } from '@/stores/auth'
import { formatQualificationFileSize, validateQualificationFile } from '@/utils/qualificationFile'
@@ -25,7 +26,11 @@ const previewOpen = ref(false)
const previewRecord = ref<TenantQualification | null>(null)
const previewUrl = ref('')
const previewLoading = ref(false)
const profileFormRef = ref<FormInstance>()
const qualificationFormRef = ref<FormInstance>()
const uploadRef = ref<UploadInstance>()
const qualificationPage = ref(1)
const qualificationPageSize = ref(10)
const selectedFileSize = ref(0)
const selectedFileType = ref('')
const qualificationFileError = ref('')
@@ -36,6 +41,28 @@ const loadError = ref('')
const selectedFile = ref<File | null>(null)
const draft = reactive<TenantCompanyProfile>({ ...store.company })
const qualificationDraft = reactive({ type: '营业执照', fileName: '', certificateNo: '' })
const profileFormRules: FormRules<TenantCompanyProfile> = {
companyName: [{ required: true, whitespace: true, message: '请输入公司名称', trigger: 'blur' }],
legalRepresentative: [{ required: true, whitespace: true, message: '请输入法人姓名', trigger: 'blur' }],
contactName: [{ required: true, whitespace: true, message: '请输入联系人姓名', trigger: 'blur' }],
contactPhone: [{ required: true, whitespace: true, message: '请输入联系电话', trigger: 'blur' }],
contactEmail: [{ type: 'email', message: '请输入正确的联系邮箱', trigger: 'blur' }],
}
const qualificationFormRules: FormRules = {
fileName: [{ required: true, message: '请选择营业执照图片', trigger: 'change' }],
}
const qualificationColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' },
{ key: 'type', prop: 'type', label: '文件类型', minWidth: 150 },
{ key: 'fileName', prop: 'fileName', label: '文件名称', minWidth: 220, showOverflowTooltip: true },
{ key: 'status', label: '状态', width: 110, slotName: 'status' },
{ key: 'expiresAt', prop: 'expiresAt', label: '有效期', width: 130 },
{ key: 'updatedAt', prop: 'updatedAt', label: '更新时间', minWidth: 160 },
]
const pagedQualifications = computed(() => {
const start = (qualificationPage.value - 1) * qualificationPageSize.value
return store.qualifications.slice(start, start + qualificationPageSize.value)
})
async function loadCompanyContext() {
loading.value = true
@@ -54,20 +81,20 @@ onMounted(() => void loadCompanyContext())
function startEdit() {
Object.assign(draft, store.company)
profileFormRef.value?.clearValidate()
editing.value = true
}
function cancelEdit() {
Object.assign(draft, store.company)
editing.value = false
profileFormRef.value?.clearValidate()
}
async function saveProfile() {
if (profileSubmitting.value) return
if (!draft.companyName || !draft.legalRepresentative || !draft.contactName || !draft.contactPhone) {
ElMessage.warning('请完整填写企业名称、法人及联系人信息')
return
}
const valid = await profileFormRef.value?.validate().then(() => true).catch(() => false)
if (!valid) return
profileSubmitting.value = true
try {
await store.updateCompany({ ...draft })
@@ -127,12 +154,14 @@ function resetQualificationDraft() {
selectedFileType.value = ''
qualificationFileError.value = ''
selectedFile.value = null
qualificationFormRef.value?.clearValidate()
}
async function submitQualification() {
if (qualificationSubmitting.value) return
if (!qualificationDraft.fileName || !selectedFile.value) {
ElMessage.warning('请选择营业执照图片')
const valid = await qualificationFormRef.value?.validate().then(() => true).catch(() => false)
if (!valid || !selectedFile.value) {
qualificationFileError.value = '请选择营业执照图片'
return
}
if (qualificationFileError.value) {
@@ -226,14 +255,20 @@ function qualificationStatus(status: string) {
<template #label><span class="tab-label"><FileCheck2 :size="16" />资质文件</span></template>
<div class="client-card qualification-panel">
<header><div><h3>企业资质</h3><p>文件更新后需等待平台重新审核,历史业务不受影响。</p></div><el-button v-if="auth.hasPermission('company:qualification:upload')" type="primary" @click="openQualificationDialog"><Upload :size="16" />更新资质</el-button></header>
<el-table class="desktop-qualification-table" :data="store.qualifications" row-key="id" empty-text="暂无企业资质记录">
<el-table-column label="操作" width="90" fixed="left"><template #default="{ row }"><el-button link type="primary" @click="previewQualification(row as TenantQualification)">查看</el-button></template></el-table-column>
<el-table-column prop="type" label="文件类型" min-width="150" />
<el-table-column prop="fileName" label="文件名称" min-width="220" show-overflow-tooltip />
<el-table-column label="状态" width="110"><template #default="{ row }"><el-tag :type="row.status === 'VERIFIED' ? 'success' : row.status === 'PENDING' ? 'warning' : 'danger'" effect="light">{{ qualificationStatus(row.status) }}</el-tag></template></el-table-column>
<el-table-column prop="expiresAt" label="有效期" width="130" />
<el-table-column prop="updatedAt" label="更新时间" min-width="160" />
</el-table>
<DataTable
v-model:page="qualificationPage"
v-model:page-size="qualificationPageSize"
class="desktop-qualification-table"
:columns="qualificationColumns"
:data="pagedQualifications"
:empty-text="'暂无企业资质记录'"
:page-sizes="[10, 20, 50]"
:total="store.qualifications.length"
row-key="id"
>
<template #actions="{ row }"><el-button link type="primary" @click="previewQualification(row as TenantQualification)">查看</el-button></template>
<template #status="{ row }"><el-tag :type="row.status === 'VERIFIED' ? 'success' : row.status === 'PENDING' ? 'warning' : 'danger'" effect="light">{{ qualificationStatus(row.status) }}</el-tag></template>
</DataTable>
<div class="mobile-qualification-list">
<article v-for="record in store.qualifications" :key="record.id">
<header><strong>{{ record.type }}</strong><el-tag :type="record.status === 'VERIFIED' ? 'success' : record.status === 'PENDING' ? 'warning' : 'danger'" effect="light">{{ qualificationStatus(record.status) }}</el-tag></header>
@@ -248,27 +283,27 @@ function qualificationStatus(status: string) {
</el-tabs>
<el-drawer v-model="editing" title="编辑企业资料" size="min(620px, 94vw)" @closed="cancelEdit">
<el-form label-position="top" class="profile-form">
<el-form ref="profileFormRef" :model="draft" :rules="profileFormRules" label-position="top" class="profile-form">
<h3>企业基本信息</h3>
<div class="form-grid-two">
<el-form-item label="公司名称" required><el-input v-model="draft.companyName" /></el-form-item>
<el-form-item label="公司名称" prop="companyName" required><el-input v-model="draft.companyName" placeholder="请输入公司名称" /></el-form-item>
<el-form-item label="统一社会信用代码"><el-input v-model="draft.creditCode" disabled /></el-form-item>
<el-form-item label="法人姓名" required><el-input v-model="draft.legalRepresentative" /></el-form-item>
<el-form-item label="法人姓名" prop="legalRepresentative" required><el-input v-model="draft.legalRepresentative" placeholder="请输入法定代表人姓名" /></el-form-item>
</div>
<h3>联系信息</h3>
<div class="form-grid-two">
<el-form-item label="联系人" required><el-input v-model="draft.contactName" /></el-form-item>
<el-form-item label="联系电话" required><el-input v-model="draft.contactPhone" /></el-form-item>
<el-form-item label="联系邮箱"><el-input v-model="draft.contactEmail" /></el-form-item>
<el-form-item label="联系人" prop="contactName" required><el-input v-model="draft.contactName" placeholder="请输入联系人姓名" /></el-form-item>
<el-form-item label="联系电话" prop="contactPhone" required><el-input v-model="draft.contactPhone" placeholder="请输入联系电话" /></el-form-item>
<el-form-item label="联系邮箱" prop="contactEmail"><el-input v-model="draft.contactEmail" placeholder="请输入联系邮箱(选填)" /></el-form-item>
</div>
</el-form>
<template #footer><div class="drawer-actions"><el-button :disabled="profileSubmitting" @click="cancelEdit">取消</el-button><el-button type="primary" :loading="profileSubmitting" @click="saveProfile">保存修改</el-button></div></template>
</el-drawer>
<el-dialog v-model="qualificationOpen" title="更新资质文件" width="min(520px, 92vw)" @closed="resetQualificationDraft">
<el-form label-position="top">
<el-form ref="qualificationFormRef" :model="qualificationDraft" :rules="qualificationFormRules" label-position="top">
<el-form-item label="文件类型"><el-input v-model="qualificationDraft.type" disabled /></el-form-item>
<el-form-item label="选择文件" required :error="qualificationFileError">
<el-form-item label="选择文件" prop="fileName" required :error="qualificationFileError">
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" :show-file-list="false" accept=".jpg,.jpeg,.png" @change="handleFileChange">
<el-button><Upload :size="16" />选择文件</el-button>
<template #tip><div class="upload-tip">仅支持 JPG、PNG,单张图片不超过 5MB。</div></template>