412 lines
19 KiB
Vue
412 lines
19 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* F006 企业详情页 - 独立页面
|
||
*
|
||
* 参考老 vue user/enterprise/enterprise-detail.vue 设计 + 新系统主题规范
|
||
* 数据源:
|
||
* - GET /user/enterprise/getInfo?enterpriseId= 企业全量信息(80+字段)
|
||
* - GET /user/performancescorerecord/page?enterpriseId= 履约评分记录
|
||
* 3 Tab: 基本信息 / 资质认证 / 履约情况
|
||
*/
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { Shop } from '@element-plus/icons-vue'
|
||
import { httpGet } from '@/utils/request'
|
||
import PageHeader from '@/components/PageHeader/PageHeader.vue'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const enterpriseId = computed(() => String(route.params.id || ''))
|
||
|
||
const loading = ref(false)
|
||
const activeTab = ref('basic')
|
||
const detail = ref<any>({})
|
||
const otherList = ref<any[]>([])
|
||
const performanceList = ref<any[]>([])
|
||
|
||
const IDENTITY_MAP: Record<number, string> = { 1: '供应商', 2: '生产厂商', 3: '渠道商', 4: '服务商' }
|
||
const STATUS_MAP: Record<number, { label: string; type: 'success' | 'warning' | 'danger' | 'info' }> = {
|
||
1: { label: '待审核', type: 'warning' },
|
||
2: { label: '已认证', type: 'success' },
|
||
3: { label: '已驳回', type: 'danger' }
|
||
}
|
||
const LEVEL_MAP: Record<number, string> = { 1: 'A级', 2: 'B级', 3: 'C级', 4: 'D级' }
|
||
|
||
const isCertified = computed(() => detail.value.status === 2)
|
||
const logoText = computed(() => (detail.value.enterpriseName || '企').trim().charAt(0))
|
||
const registerYear = computed(() => {
|
||
const t = detail.value.registerTime
|
||
return t ? String(t).slice(0, 10) : '--'
|
||
})
|
||
const contactInitial = computed(() => (detail.value.linkMan || '联').trim().charAt(0))
|
||
const tags = computed<string[]>(() => {
|
||
const raw = detail.value.supplierLabelList || detail.value.supplierLabel || ''
|
||
if (Array.isArray(raw)) return raw
|
||
return String(raw).split(/[,,、;;\s]+/).map((s: string) => s.trim()).filter(Boolean)
|
||
})
|
||
const productList = computed<string[]>(() => {
|
||
const raw = detail.value.mainProducts
|
||
if (!raw) return []
|
||
return String(raw).split(/[,,、;;\s]+/).map((s: string) => s.trim()).filter(Boolean)
|
||
})
|
||
|
||
function maskMobile(m: string | number | null | undefined): string {
|
||
if (m === undefined || m === null || m === '') return '--'
|
||
const s = String(m)
|
||
return s.length === 11 ? `${s.slice(0, 3)}****${s.slice(7)}` : s
|
||
}
|
||
function fmtDate(d: string | null | undefined): string {
|
||
if (!d) return '--'
|
||
return String(d).slice(0, 19).replace('T', ' ')
|
||
}
|
||
function fmtMoney(n: any): string {
|
||
const v = Number(n || 0)
|
||
return v ? `¥ ${v.toLocaleString()}` : '¥ 0'
|
||
}
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const resp: any = await httpGet('/user/enterprise/getInfo', { enterpriseId: enterpriseId.value })
|
||
if (resp) {
|
||
detail.value = resp
|
||
otherList.value = (resp.enterpriseOtherList || []).map((it: any) => ({
|
||
...it,
|
||
imageList: it.images ? String(it.images).split(',').map((s: string) => s.trim()).filter(Boolean) : []
|
||
}))
|
||
}
|
||
} catch { detail.value = {} } finally { loading.value = false }
|
||
|
||
try {
|
||
const r: any = await httpGet('/user/performancescorerecord/page', { enterpriseId: enterpriseId.value, page: 1, limit: 20 })
|
||
performanceList.value = (r?.list || []).map((it: any) => ({
|
||
date: it.createDate,
|
||
content: `评分员「${it.userName || '--'}」发起履约评分`,
|
||
score: it.averageScore ?? it.score
|
||
}))
|
||
} catch { performanceList.value = [] }
|
||
}
|
||
|
||
function goStore() {
|
||
const eid = detail.value.enterpriseId || detail.value.id
|
||
if (!eid) return
|
||
window.open(`/v2/shop/${eid}`, '_blank')
|
||
}
|
||
|
||
onMounted(load)
|
||
</script>
|
||
|
||
<template>
|
||
<div class="ent-detail" v-loading="loading">
|
||
<PageHeader title="企业详情" :sub="`入驻企业信息总览`">
|
||
<el-button @click="router.back()">返回列表</el-button>
|
||
</PageHeader>
|
||
|
||
<!-- ===== Header 区: logo + 企业名 + 认证标签 + meta ===== -->
|
||
<div class="detail-header">
|
||
<div class="header-logo">
|
||
<el-image v-if="detail.enterpriseLogo" :src="detail.enterpriseLogo" fit="cover" class="logo-img" />
|
||
<span v-else class="logo-fallback">{{ logoText }}</span>
|
||
</div>
|
||
<div class="header-main">
|
||
<div class="header-title-row">
|
||
<h2 class="ent-name">{{ detail.enterpriseName || '--' }}</h2>
|
||
<span v-for="(t, i) in tags" :key="i" class="name-tag">{{ t }}</span>
|
||
<el-tag v-if="STATUS_MAP[detail.status]" :type="STATUS_MAP[detail.status].type" effect="light" round>{{ STATUS_MAP[detail.status].label }}</el-tag>
|
||
<el-tag v-if="detail.enabled === 1" type="info" effect="plain" round>已禁用</el-tag>
|
||
</div>
|
||
<div class="header-meta">
|
||
<span v-if="detail.industry" class="meta-item">{{ detail.industry }}</span>
|
||
<span v-if="registerYear !== '--'" class="meta-item">{{ registerYear }} 成立</span>
|
||
<span v-if="detail.creditCode" class="meta-item">信用代码 {{ detail.creditCode }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<div class="header-stats">
|
||
<div class="stat-cell">
|
||
<div class="stat-value">{{ LEVEL_MAP[detail.supplierLevel as number] || '--' }}</div>
|
||
<div class="stat-label">供应商等级</div>
|
||
</div>
|
||
<div class="stat-cell">
|
||
<div class="stat-value">{{ detail.currentScore ?? '--' }}</div>
|
||
<div class="stat-label">当前评分</div>
|
||
</div>
|
||
<div class="stat-cell">
|
||
<div class="stat-value">{{ detail.enterpriseRate ?? '--' }}</div>
|
||
<div class="stat-label">企业评级</div>
|
||
</div>
|
||
</div>
|
||
<el-button type="primary" class="store-btn" @click="goStore" :disabled="!detail.enterpriseId && !detail.id">
|
||
<el-icon style="margin-right:6px"><Shop /></el-icon>进入店铺
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ===== Tab 导航 ===== -->
|
||
<div class="tab-nav">
|
||
<div class="tab-item" :class="{ active: activeTab === 'basic' }" @click="activeTab = 'basic'">基本信息</div>
|
||
<div class="tab-item" :class="{ active: activeTab === 'qual' }" @click="activeTab = 'qual'">资质认证</div>
|
||
<div class="tab-item" :class="{ active: activeTab === 'perf' }" @click="activeTab = 'perf'">履约情况</div>
|
||
</div>
|
||
|
||
<!-- ===== Tab 1: 基本信息 ===== -->
|
||
<div v-show="activeTab === 'basic'" class="tab-body">
|
||
<div class="info-grid">
|
||
<div class="info-col">
|
||
<section class="info-card">
|
||
<div class="card-title">公司信息</div>
|
||
<div class="info-grid-2">
|
||
<div class="info-item"><span class="label">公司名称</span><span class="value">{{ detail.enterpriseName || '--' }}</span></div>
|
||
<div class="info-item"><span class="label">注册时间</span><span class="value">{{ fmtDate(detail.registerTime) }}</span></div>
|
||
<div class="info-item"><span class="label">所属行业</span><span class="value">{{ detail.industry || '--' }}</span></div>
|
||
<div class="info-item"><span class="label">企业身份</span><span class="value">{{ IDENTITY_MAP[detail.enterpriseIdentity as number] || detail.enterpriseIdentity || '--' }}</span></div>
|
||
<div class="info-item"><span class="label">企业类型</span><span class="value">{{ detail.enterpriseType ?? '--' }}</span></div>
|
||
<div class="info-item"><span class="label">统一信用代码</span><span class="value">{{ detail.creditCode || detail.orgNo || '--' }}</span></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="info-card">
|
||
<div class="card-title">地址信息</div>
|
||
<div class="address-box">
|
||
<div class="addr-line">{{ [detail.provinceName, detail.cityName, detail.areaName].filter(Boolean).join(' ') || '--' }}</div>
|
||
<div class="value">{{ detail.enterpriseAddress || '--' }}</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="info-col">
|
||
<section class="info-card">
|
||
<div class="card-title">联系人信息</div>
|
||
<div class="contact-profile">
|
||
<div class="contact-avatar">{{ contactInitial }}</div>
|
||
<div class="contact-name">{{ detail.linkMan || '--' }}</div>
|
||
</div>
|
||
<div class="contact-methods">
|
||
<div class="contact-method">
|
||
<span class="label">联系电话</span><span class="value">{{ maskMobile(detail.linkMobile) }}</span>
|
||
</div>
|
||
<div class="contact-method">
|
||
<span class="label">电子邮箱</span><span class="value">{{ detail.email || '--' }}</span>
|
||
</div>
|
||
<div class="contact-method">
|
||
<span class="label">管理员</span><span class="value">{{ detail.administrator || '--' }} ({{ maskMobile(detail.administratorMobile) }})</span>
|
||
</div>
|
||
<div class="contact-method">
|
||
<span class="label">业务员</span><span class="value">{{ detail.salesperson || '暂未分配' }}</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="info-card full">
|
||
<div class="card-title">经营信息</div>
|
||
<div class="info-grid-2">
|
||
<div class="info-item span2"><span class="label">经营范围</span><span class="value">{{ detail.scope || detail.scopeOfLicence || '--' }}</span></div>
|
||
<div class="info-item span2"><span class="label">主营产品</span>
|
||
<span class="product-tags">
|
||
<span v-for="(p, i) in productList" :key="i" class="product-tag">{{ p }}</span>
|
||
<span v-if="!productList.length" class="value">--</span>
|
||
</span>
|
||
</div>
|
||
<div class="info-item"><span class="label">银行</span><span class="value">{{ detail.bankOfDeposit || '--' }}</span></div>
|
||
<div class="info-item"><span class="label">开户名称</span><span class="value">{{ detail.accountName || '--' }}</span></div>
|
||
<div class="info-item"><span class="label">账号</span><span class="value">{{ detail.accountCard || '--' }}</span></div>
|
||
<div class="info-item"><span class="label">账户余额</span><span class="value">{{ fmtMoney(detail.accountBalance) }}</span></div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- ===== Tab 2: 资质认证 ===== -->
|
||
<div v-show="activeTab === 'qual'" class="tab-body">
|
||
<section class="info-card full">
|
||
<div class="card-title">营业执照</div>
|
||
<el-image v-if="detail.licenseImg" :src="detail.licenseImg" fit="contain" class="cert-img" :preview-src-list="[detail.licenseImg]" preview-teleported />
|
||
<div v-else class="cert-empty">暂无营业执照</div>
|
||
</section>
|
||
|
||
<section class="info-card full">
|
||
<div class="card-title">法人身份证</div>
|
||
<div class="idcard-row">
|
||
<div class="idcard-cell">
|
||
<span class="label">正面</span>
|
||
<el-image v-if="detail.frontImage" :src="detail.frontImage" fit="cover" class="idcard-img" :preview-src-list="[detail.frontImage]" preview-teleported />
|
||
<div v-else class="cert-empty small">暂无</div>
|
||
</div>
|
||
<div class="idcard-cell">
|
||
<span class="label">反面</span>
|
||
<el-image v-if="detail.backImage" :src="detail.backImage" fit="cover" class="idcard-img" :preview-src-list="[detail.backImage]" preview-teleported />
|
||
<div v-else class="cert-empty small">暂无</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="detail.legalMan" class="legal-row"><span class="label">法人代表</span><span class="value">{{ detail.legalMan }}</span></div>
|
||
</section>
|
||
|
||
<section class="info-card full">
|
||
<div class="card-title">资质证书</div>
|
||
<template v-if="otherList.length">
|
||
<div v-for="(cert, i) in otherList" :key="i" class="cert-card">
|
||
<div class="cert-info">
|
||
<div class="cert-row"><span class="label">资质名称</span><span class="value">{{ cert.qualificationName || '--' }}</span></div>
|
||
<div class="cert-row"><span class="label">证书编号</span><span class="value">{{ cert.certificateNumber || '--' }}</span></div>
|
||
<div class="cert-row"><span class="label">有效期至</span><span class="value">{{ cert.expirationDate || '--' }}</span></div>
|
||
</div>
|
||
<div v-if="cert.imageList?.length" class="cert-imgs">
|
||
<el-image v-for="(img, j) in cert.imageList" :key="j" :src="img" fit="cover" class="cert-thumb" :preview-src-list="cert.imageList" preview-teleported />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div v-else class="cert-empty">暂无资质证书</div>
|
||
</section>
|
||
|
||
<section class="info-card full">
|
||
<div class="card-title">企业授权书</div>
|
||
<el-image v-if="detail.enterpriseAccredit" :src="detail.enterpriseAccredit" fit="cover" class="cert-img tall" :preview-src-list="[detail.enterpriseAccredit]" preview-teleported />
|
||
<div v-else class="cert-empty">暂无授权书</div>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- ===== Tab 3: 履约情况 ===== -->
|
||
<div v-show="activeTab === 'perf'" class="tab-body">
|
||
<section class="info-card full">
|
||
<div class="card-title">履约评分记录</div>
|
||
<template v-if="performanceList.length">
|
||
<div v-for="(item, i) in performanceList" :key="i" class="perf-item">
|
||
<span class="perf-date">{{ fmtDate(item.date) }}</span>
|
||
<span class="perf-content">{{ item.content }}</span>
|
||
<span class="perf-score">{{ item.score ?? '--' }} 分</span>
|
||
</div>
|
||
</template>
|
||
<div v-else class="cert-empty">暂无履约评分记录</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped lang="scss">
|
||
.ent-detail { max-width: 1400px; }
|
||
|
||
/* ===== Header ===== */
|
||
.detail-header {
|
||
display: flex; gap: 24px; align-items: center;
|
||
background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg); padding: 24px 28px; margin-bottom: 16px;
|
||
}
|
||
.header-logo {
|
||
flex: 0 0 88px; width: 88px; height: 88px; border-radius: 12px; overflow: hidden;
|
||
background: linear-gradient(135deg, var(--primary), var(--primary-soft, #93c5fd));
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.logo-img { width: 100%; height: 100%; }
|
||
.logo-fallback { color: #fff; font-size: 32px; font-weight: 700; }
|
||
.header-main { flex: 1; min-width: 0; }
|
||
.header-title-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
|
||
.ent-name { font-size: 22px; font-weight: 700; margin: 0; color: var(--ink); }
|
||
.name-tag {
|
||
background: var(--primary-soft, #e0ecff); color: var(--primary);
|
||
font-size: 12px; padding: 2px 12px; border-radius: 20px; white-space: nowrap;
|
||
}
|
||
.header-meta { display: flex; gap: 20px; flex-wrap: wrap; color: var(--ink-2); font-size: 13px; margin-bottom: 10px; }
|
||
.meta-item::before { content: ''; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--primary); margin-right: 6px; vertical-align: middle; }
|
||
.header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; }
|
||
.header-stats { display: flex; gap: 32px; }
|
||
.stat-cell { text-align: center; }
|
||
.stat-value { font-size: 20px; font-weight: 700; color: var(--ink); }
|
||
.stat-label { font-size: 12px; color: var(--ink-3); margin-top: 4px; }
|
||
.store-btn { font-weight: 600; }
|
||
|
||
/* ===== Tabs ===== */
|
||
.tab-nav {
|
||
display: flex; gap: 36px; border-bottom: 1px solid var(--border);
|
||
padding: 0 8px; margin-bottom: 20px;
|
||
}
|
||
.tab-item {
|
||
font-size: 15px; color: var(--ink-3); padding: 10px 4px; cursor: pointer;
|
||
border-bottom: 2px solid transparent; transition: all .2s;
|
||
}
|
||
.tab-item:hover { color: var(--ink); }
|
||
.tab-item.active { color: var(--primary); font-weight: 600; border-bottom-color: var(--primary); }
|
||
|
||
/* ===== Body / cards ===== */
|
||
.tab-body { display: flex; flex-direction: column; gap: 16px; }
|
||
.info-grid { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 16px; }
|
||
.info-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
||
.info-card {
|
||
background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg); padding: 22px 24px;
|
||
}
|
||
.info-card.full { width: 100%; }
|
||
.card-title {
|
||
font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 18px;
|
||
padding-left: 10px; border-left: 3px solid var(--primary);
|
||
}
|
||
.info-grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 48px; }
|
||
.info-item { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
|
||
.info-item.span2 { grid-column: span 2; }
|
||
.label { font-size: 12px; color: var(--ink-3); }
|
||
.value { font-size: 14px; color: var(--ink); font-weight: 500; word-break: break-word; }
|
||
|
||
/* 地址 */
|
||
.address-box {
|
||
background: var(--bg, #f6f8fb); border: 1px dashed var(--border);
|
||
border-radius: 10px; padding: 16px 18px; display: flex; flex-direction: column; gap: 8px;
|
||
}
|
||
.addr-line { font-size: 13px; color: var(--primary); font-weight: 600; }
|
||
|
||
/* 联系人 */
|
||
.contact-profile { display: flex; flex-direction: column; align-items: center; margin-bottom: 20px; }
|
||
.contact-avatar {
|
||
width: 72px; height: 72px; border-radius: 50%;
|
||
background: var(--primary-soft, #e0ecff); color: var(--primary);
|
||
font-size: 26px; font-weight: 700; display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.contact-name { margin-top: 12px; font-size: 16px; font-weight: 700; color: var(--ink); }
|
||
.contact-methods { display: flex; flex-direction: column; gap: 14px; }
|
||
.contact-method { display: flex; flex-direction: column; gap: 4px; }
|
||
|
||
/* 产品标签 */
|
||
.product-tags { display: flex; flex-wrap: wrap; gap: 8px; }
|
||
.product-tag {
|
||
background: var(--primary-soft, #e0ecff); color: var(--primary);
|
||
font-size: 12px; padding: 3px 14px; border-radius: 6px;
|
||
}
|
||
|
||
/* ===== 资质 ===== */
|
||
.cert-img { width: 220px; height: 150px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg, #f6f8fb); }
|
||
.cert-img.tall { width: 160px; height: 200px; }
|
||
.cert-empty {
|
||
width: 220px; height: 120px; border: 1px dashed var(--border); border-radius: 8px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
color: var(--ink-3); font-size: 13px; background: var(--bg, #f6f8fb);
|
||
}
|
||
.cert-empty.small { width: 150px; height: 100px; }
|
||
.idcard-row { display: flex; gap: 24px; margin-bottom: 16px; }
|
||
.idcard-cell { display: flex; flex-direction: column; gap: 8px; }
|
||
.idcard-img { width: 180px; height: 120px; border-radius: 8px; border: 1px solid var(--border); }
|
||
.legal-row { display: flex; gap: 12px; align-items: baseline; }
|
||
|
||
.cert-card {
|
||
border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px;
|
||
margin-bottom: 14px; background: var(--bg, #fafbfd);
|
||
}
|
||
.cert-row { display: flex; gap: 12px; margin-bottom: 6px; }
|
||
.cert-row .label { min-width: 76px; }
|
||
.cert-imgs { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
|
||
.cert-thumb { width: 100px; height: 120px; border-radius: 6px; border: 1px solid var(--border); }
|
||
|
||
/* ===== 履约 ===== */
|
||
.perf-item {
|
||
display: flex; align-items: center; gap: 16px; padding: 12px 0;
|
||
border-bottom: 1px solid var(--border-light, var(--border));
|
||
}
|
||
.perf-item:last-child { border-bottom: none; }
|
||
.perf-date { font-size: 13px; color: var(--ink-3); min-width: 140px; }
|
||
.perf-content { flex: 1; font-size: 14px; color: var(--ink); }
|
||
.perf-score { font-size: 15px; font-weight: 700; color: var(--primary); }
|
||
|
||
@media (max-width: 1100px) {
|
||
.info-grid { grid-template-columns: 1fr; }
|
||
.header-stats { display: none; }
|
||
}
|
||
</style>
|