This commit is contained in:
2026-10-20 23:34:11 +08:00
parent f155976f52
commit 13e4aa56ec
321 changed files with 6756 additions and 2110 deletions
@@ -1,4 +1,5 @@
<script setup lang="ts">
import '@/styles/adminWorkbench.css'
import InlineHelp from '@/shared/components/InlineHelp.vue'
import '@/styles/adminDensity.css'
import '@/modules/customers/customerDensity.css'
@@ -166,7 +167,7 @@ async function changeStatus() {
const verb = next === 'ACTIVE' ? '启用' : '停用'
try {
await ElMessageBox.confirm(`确认${verb}客户“${customer.value.tenantName}”吗?`, customerDialogTitle(`${verb}客户`, customer.value), {
customClass: 'admin-density-confirm', type: 'warning', confirmButtonText: `确认${verb}`, cancelButtonText: '取消',
customClass: 'admin-density-confirm admin-workbench', type: 'warning', confirmButtonText: `确认${verb}`, cancelButtonText: '取消',
})
statusChanging.value = true
customer.value = await changePlatformTenantStatus(tenantId.value, next)
@@ -217,7 +218,7 @@ onBeforeUnmount(() => {
FORM: 精确继承原型的详情工作台结构,以现有后台组件体系实现。
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, and DESIGN.md
-->
<AdminPage as="div" class="customer-detail-page admin-density customer-density">
<AdminPage as="div" class="customer-detail-page admin-density customer-density admin-workbench">
<LoadErrorState v-if="loadError" title="客户详情暂时无法加载" :message="loadError.message" :loading="loading" @retry="loadDetail">
<template #details><ApiErrorMeta :details="loadError" /></template>
</LoadErrorState>
@@ -287,8 +288,8 @@ onBeforeUnmount(() => {
</section>
<section class="detail-panel user-panel">
<div class="panel-title"><div><h2>企业用户<InlineHelp title="账号信息说明"><p>登录手机号属于账号信息;请在成员/账号管理中单独维护,修改会影响登录</p></InlineHelp></h2></div><UsersRound :size="19" /></div>
<div v-if="enterpriseMembers.length" class="user-table-wrap">
<table><thead><tr><th>账号</th><th>成员类型</th><th>登录手机号</th><th>邮箱</th><th>角色</th><th>状态</th></tr></thead>
<div v-if="enterpriseMembers.length" class="user-table-wrap workbench-evidence">
<table class="density-table"><thead><tr><th>账号</th><th>成员类型</th><th>登录手机号</th><th>邮箱</th><th>角色</th><th>状态</th></tr></thead>
<tbody><tr v-for="member in enterpriseMembers" :key="String(member.membershipId)">
<td><div class="user-identity"><span>{{ (member.displayName || member.username || '?').slice(0, 1) }}</span><div><strong>{{ displayText(member.username) }}</strong><small>{{ displayText(member.displayName) }}</small></div></div></td>
<td>{{ member.memberType === 'OWNER' ? '企业管理员' : '企业员工' }}</td>
@@ -309,7 +310,7 @@ onBeforeUnmount(() => {
</div>
</DetailPageLayout>
<AppDialog class="admin-density-dialog customer-density-dialog customer-qualification-preview" v-model="previewOpen" :title="customerDialogTitle('查看资质文件', customer)" fullscreenable width="min(920px, calc(100vw - 28px))" append-to-body :show-footer="false">
<AppDialog class="admin-density-dialog customer-density-dialog customer-qualification-preview admin-workbench is-dialog" v-model="previewOpen" :title="customerDialogTitle('查看资质文件', customer)" fullscreenable width="min(920px, calc(100vw - 28px))" append-to-body :show-footer="false">
<p class="density-dialog-note customer-preview-filename">{{ previewName }}</p>
<div class="density-dialog-section qualification-preview" data-tone="source"><img v-if="previewKind === 'image'" :src="previewUrl" :alt="`${previewName}预览`" /><iframe v-else :src="previewUrl" :title="`${previewName}预览`" /></div>
</AppDialog>
@@ -317,7 +318,7 @@ onBeforeUnmount(() => {
</template>
<style scoped>
.detail-tabs { border-bottom: 1px solid var(--cc-border); }
.detail-tabs { border-bottom: 1px solid var(--workbench-border); }
.customer-detail-page { min-width: 0; }
.detail-header { display:flex; min-width:0; align-items:center; gap:var(--admin-space-5, 12px); }
@@ -329,25 +330,25 @@ onBeforeUnmount(() => {
.detail-panel { min-width:0; }
.contact-panel { grid-column:1 / -1; }
.user-panel { grid-column:1 / -1; }
.panel-title { display:flex; justify-content:space-between; border-bottom:1px solid var(--cc-border); }
.panel-title h2 { margin:0; color:#344054; }.panel-title p { margin:5px 0 0; color:var(--cc-muted); }.panel-title > svg { color:var(--cc-primary); }
.panel-title { display:flex; justify-content:space-between; border-bottom:1px solid var(--workbench-border); }
.panel-title h2 { margin:0; color:var(--workbench-text); }.panel-title p { margin:5px 0 0; color:var(--workbench-muted); }.panel-title > svg { color:var(--workbench-primary); }
.info-grid { margin:0; }.info-grid > div.wide { grid-column:1 / -1; }
.qualification-list { display:flex; flex-direction:column; }
.qualification-list button { display:grid; width:100%; align-items:center; background:var(--admin-surface, #fff); color:inherit; text-align:left; cursor:pointer; }
.qualification-list button:hover { border-color:#b9c3f5; background:#fafbff; }.qualification-list button:disabled { cursor:wait; opacity:.65; }
.qualification-list button { display:grid; width:100%; align-items:center; background:var(--workbench-surface); color:inherit; text-align:left; cursor:pointer; }
.qualification-list button:hover { border-color:var(--workbench-accent-border); background:var(--workbench-surface-soft); }.qualification-list button:disabled { cursor:wait; opacity:.65; }
.qualification-list strong { display:block; }
.qualification-list small { display:block; }.qualification-list small { margin-top:3px; color:var(--cc-muted); }.file-date { margin-top:3px; color:var(--cc-muted); }.file-date { margin:0; white-space:nowrap; }
.section-empty { display:flex; align-items:center; justify-content:center; flex-direction:column; color:#98a2b3; text-align:center; }
.module-empty { display:flex; align-items:center; justify-content:center; flex-direction:column; color:#98a2b3; text-align:center; }
.section-empty strong { color:#475467; font-size:var(--admin-font-body, 13px); }
.module-empty strong { color:#475467; font-size:var(--admin-font-body, 13px); }.section-empty span { max-width:600px; margin:0; color:var(--cc-muted); line-height:1.7; }.module-empty p { max-width:600px; margin:0; color:var(--cc-muted); font-size:var(--admin-font-caption, 11px); line-height:1.7; }
.section-empty button { border:0; background:transparent; color:var(--cc-primary); cursor:pointer; }.section-empty.is-error svg { color:#d92d20; }
.qualification-list small { display:block; }.qualification-list small { margin-top:3px; color:var(--workbench-muted); }.file-date { margin-top:3px; color:var(--workbench-muted); }.file-date { margin:0; white-space:nowrap; }
.section-empty { display:flex; align-items:center; justify-content:center; flex-direction:column; color:var(--workbench-muted); text-align:center; }
.module-empty { display:flex; align-items:center; justify-content:center; flex-direction:column; color:var(--workbench-muted); text-align:center; }
.section-empty strong { color:var(--workbench-text); font-size:var(--admin-font-body, 13px); }
.module-empty strong { color:var(--workbench-text); font-size:var(--admin-font-body, 13px); }.section-empty span { max-width:600px; margin:0; color:var(--workbench-muted); line-height:1.7; }.module-empty p { max-width:600px; margin:0; color:var(--workbench-muted); font-size:var(--admin-font-caption, 11px); line-height:1.7; }
.section-empty button { border:0; background:transparent; color:var(--workbench-primary); cursor:pointer; }.section-empty.is-error svg { color:#d92d20; }
.user-table-wrap { overflow-x:auto; }
table { width:100%; min-width:720px; border-collapse:collapse; font-size:var(--admin-font-caption, 12px); }th { padding:var(--admin-space-4, 10px) var(--admin-space-5, 12px); background:var(--admin-label-bg, #f8fafc); color:#667085; text-align:left; font-weight:650; }td { padding:var(--admin-space-5, 12px); border-top:1px solid #edf0f4; color:#344054; }
.user-identity { display:flex; align-items:center; gap:9px; }.user-identity > span { width:34px; height:34px; flex:0 0 auto; place-items:center; border-radius:50%; background:#eef1ff; color:var(--cc-primary); font-weight:700; }.user-identity strong { display:block; }.user-identity small { display:block; }.user-identity small { margin-top:3px; color:var(--cc-muted); }.role-list { display:flex; flex-wrap:wrap; gap:5px; }
table { min-width:720px; }
.user-identity { display:flex; align-items:center; gap:9px; }.user-identity > span { width:34px; height:34px; flex:0 0 auto; place-items:center; border-radius:50%; background:var(--workbench-accent-soft); color:var(--workbench-primary); font-weight:700; }.user-identity strong { display:block; }.user-identity small { display:block; }.user-identity small { margin-top:3px; color:var(--workbench-muted); }.role-list { display:flex; flex-wrap:wrap; gap:5px; }
.member-contact span { display:block; }
.member-contact small { display:block; }.member-contact small { margin-top:3px; color:var(--cc-muted); }.module-empty > svg { color:#8797b2; }.module-empty p { max-width:520px; font-size:var(--admin-font-caption, 12px); }
.contract-note { padding:var(--admin-space-6, 16px) 18px; border-radius:10px; background:var(--admin-label-bg, #f8fafc); }.contract-note strong { color:#344054; font-size:var(--admin-font-caption, 12px); }.contract-note p { margin:5px 0 0; color:var(--cc-muted); font-size:var(--admin-font-caption, 11px); line-height:1.7; }
.member-contact small { display:block; }.member-contact small { margin-top:3px; color:var(--workbench-muted); }.module-empty > svg { color:var(--workbench-muted); }.module-empty p { max-width:520px; font-size:var(--admin-font-caption, 12px); }
.contract-note { padding:var(--admin-space-6, 16px) 18px; border-radius:10px; background:var(--workbench-surface-soft); }.contract-note strong { color:var(--workbench-text); font-size:var(--admin-font-caption, 12px); }.contract-note p { margin:5px 0 0; color:var(--workbench-muted); font-size:var(--admin-font-caption, 11px); line-height:1.7; }
@media (max-width:960px) { .basic-layout { grid-template-columns:1fr; }.contact-panel,.user-panel { grid-column:auto; }.qualification-panel { order:3; }.user-panel { order:4; }.info-grid--three { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:720px) { .detail-header { align-items:flex-start; flex-wrap:wrap; }.detail-page-toolbar { width:100%; flex-basis:100%; }.company-mark { width:40px; height:40px; }.header-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); width:100%; }.header-actions :deep(.el-tag) { grid-column:1 / -1; justify-self:start; }.header-actions :deep(.el-button) { min-height:44px; }.detail-panel { padding:16px; }.info-grid { grid-template-columns:1fr; }.info-grid > div.wide { grid-column:auto; } }