@@ -3,7 +3,7 @@ import '@/styles/adminDensity.css'
import '@/modules/customers/customerDensity.css'
import { computed , nextTick , onBeforeUnmount , onMounted , reactive , ref , watch } from 'vue'
import { useRoute , useRouter } from 'vue-router'
import { Building2 , CheckCircle2 , CircleCheck , FileCheck2 , FileText , ShieldCheck , ShieldX } from '@lucide/vue'
import { CheckCircle2 , FileText , ShieldCheck , ShieldX } from '@lucide/vue'
import { ElMessage , ElMessageBox , type FormInstance , type FormRules } from 'element-plus'
import { getApiErrorDetails , getApiErrorMessage , type ApiErrorDetails } from '@/shared/api/http'
import RowActions , { type RowActionItem } from '@/shared/components/RowActions.vue'
@@ -42,7 +42,7 @@ import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.v
import QueryToolbar , { type QueryField , type QueryModel } from '@/shared/components/QueryToolbar.vue'
import AdminPage from '@/shared/components/AdminPage.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import FormSection from '@/shared/components/FormSection.vu e'
import { customerDialogTitle } from '../customerDialogTitl e'
import ListPageLayout from '@/shared/components/ListPageLayout.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
@@ -227,7 +227,8 @@ const customerColumns: DataTableColumn[] = [
let loadSequence = 0
let reviewLoadSequence = 0
let editorLoadSequence = 0
const editorTitle = computed ( ( ) => editingId . value ? '编辑客户' : '新增客户' )
const editorCustomer = computed ( ( ) => result . value . records . find ( row => String ( row . id ) === editingId . value ) || { tenantName : form . value . tenantName } )
const editorTitle = computed ( ( ) => editingId . value ? customerDialogTitle ( '编辑客户' , editorCustomer . value ) : '新增客户' )
const editorDialogWidth = 'min(1120px, calc(100vw - 28px))'
const canUploadQualifications = computed ( ( ) => ! editorStatus . value
|| [ 'PROFILE_REQUIRED' , 'REJECTED' , 'ACTIVE' , 'DISABLED' ] . includes ( editorStatus . value ) )
@@ -641,7 +642,7 @@ async function submitReview() {
const missing = qualifications . value . filter ( ( item ) => item . required && ! item . uploaded )
for ( const item of missing ) item . error = ` ${ item . label } 为提交审核必填文件 `
if ( missing . length ) { ElMessage . warning ( ` 请先上传 ${ missing . map ( ( item ) => item . label ) . join ( '、' ) } ` ) ; return }
await ElMessageBox . confirm ( '提交后客户资料与资质将进入企业审核,审核完成前不能继续替换文件。确认提交吗?' , '提交企业审核' , { customClass : 'admin-density-confirm' , type : 'warning' , confirmButtonText : '确认提交' , cancelButtonText : '继续完善' } )
await ElMessageBox . confirm ( '提交后客户资料与资质将进入企业审核,审核完成前不能继续替换文件。确认提交吗?' , customerDialogTitle ( '提交企业审核' , editorCustomer . value ) , { customClass : 'admin-density-confirm' , type : 'warning' , confirmButtonText : '确认提交' , cancelButtonText : '继续完善' } )
const saved = await submitPlatformTenantReview ( editingId . value )
editorStatus . value = saved . status ; ElMessage . success ( '审核单已提交' )
editorOpen . value = false ; await loadCustomers ( )
@@ -730,7 +731,7 @@ function handleMoreAction(row: PlatformTenantResource, command: string) {
async function toggleStatus ( row : PlatformTenantResource ) {
const next = row . status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE' ; const verb = next === 'ACTIVE' ? '启用' : '停用'
try {
await ElMessageBox . confirm ( ` 确认 ${ verb } 客户“ ${ row . tenantName } ”吗? ` , ` ${ verb } 客户 ` , { customClass : 'admin-density-confirm' , type : 'warning' , confirmButtonText : ` 确认 ${ verb } ` , cancelButtonText : '取消' } )
await ElMessageBox . confirm ( ` 确认 ${ verb } 客户“ ${ row . tenantName } ”吗? ` , customerDialogTitle ( ` ${ verb } 客户 ` , row ) , { customClass : 'admin-density-confirm' , type : 'warning' , confirmButtonText : ` 确认 ${ verb } ` , cancelButtonText : '取消' } )
rowActionId . value = String ( row . id ) ; await changePlatformTenantStatus ( row . id , next )
ElMessage . success ( ` 客户已 ${ verb } ` ) ; await loadCustomers ( )
} catch ( error ) {
@@ -1081,30 +1082,29 @@ onMounted(() => { void loadCustomers() })
< / DataTable > < / section >
< / ListPageLayout >
< AppDialog v-model = "editorOpen" :title="editorTitle" :width="editorDialogWidth" class="admin-density-dialog customer-density-dialog customer-editor-dialog" :loading="editorLoading" :confirm-loading="saving" destroy -on -close >
< el-form ref = "formRef" v-loading = "editorLoading" :model="form" :rules="rules" label-position="top" class="drawer-form " :disabled="editorLoading || saving || submitting || uploadingQualifications" >
< FormSection title = "基础资料" description = "维护工商登记和企业主体信息。" > < div class = "form-grid three-cols" >
< el-form-item label = "公司名称" prop = "tenantName" required > < el-input v-model = "form.tenantName" placeholder="请输入工商登记公司名称" maxlength="128" / > < / el-form-item >
< el-form-item label = "客户昵 称" prop = "customerNickn ame" > < el-input v-model = "form.customerNickname" placeholder="不填默认生成中文拼音大写首字母,如 SZWL" maxlength="32" /><small class="field-hint" > 创建后仍可在客户资料中修改 。 < / small > < / el -form -item >
< el-form-item label = "营业执照企业名称" prop = "legalName" required > < el-input v-model = "form.legalName" placeholder="请输入营业执照上的企业名称" maxlength="128" / > < / el-form-item >
< el-form-item label = "法定代表人 " prop = "legalRe pre sentativ e" > < el-input v-model = "form.legalRepresentative" placeholder="请输入法定代表人姓名(选填)" maxlength="64" / > < / el-form-item >
< el-form-item label = "企业类型 " prop = "enterpriseType " > < el-input v-model = "form.enterpriseType" placeholder="如:有限责任公司" maxlength="64" / > < / el-form-item >
< el-form-item label = "成立日期 " prop = "establishedDate" > < el-date-picker style = "width: 100%;" v-model = "form.establishedDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择成立日期" :disabled-date="(date: Date) => date.getTime() > Date.now()" / > < / el-form-item >
< el-form-item label = "注册资本" prop = "registeredCapital" > < div class = "capital-input" > < el-input-number v-model = "form.registeredCapital" :min="0" :precision="2" controls-position="right" placeholder="请输入注册资本(选填)" /><el-select v-model="form.registeredCapitalCurrency" aria-label="注册资本币种"><el-option label="人民币 CNY" value="CNY" /><el-option label="美元 USD" value="USD" /><el-option label="港币 HKD" value="HKD" / > < / el-select > < / div > < / el-form-item >
< / div > < el-form-item label = "注册地址" prop = "registeredAddress" > < el-input v-model = "form.registeredAddress" placeholder="请输入省市区、街道及门牌信息" maxlength="500" / > < / el-form-item > < / FormSection >
< AppDialog v-model = "editorOpen" :title="editorTitle" :width="editorDialogWidth" class="admin-density-dialog customer-density-dialog customer-editor-dialog" :loading="editorLoading" :confirm-loading="saving" fullscreenable destroy -on -close >
< el-form ref = "formRef" v-loading = "editorLoading" :model="form" :rules="rules" label-position="left" label-width="144px" class="density-form density-dialog-sections " :disabled="editorLoading || saving || submitting || uploadingQualifications" >
< section class = "density-dialog-section" data -tone = " work " > < h3 class = "density-dialog-section__title" > 基础资料 < / h3 >
< table class = "density-table density-table--editable customer-editor-table" aria -label = " 基础资料 " > < tbody >
< tr > < td > < el-form-item label = "公司名 称" prop = "tenantN ame" required > < el-input v-model = "form.tenantName" placeholder="请输入工商登记公司名称" maxlength="128" /></el-form-item></td><td><el-form-item label="客户昵称" prop="customerNickname"><el-input v-model="form. customerNickname" placeholder="不填默认生成中文拼音大写首字母,如 SZWL" maxlength="32" /><small class="field-hint" > 创建后仍可在客户资料中修改 。 < / small > < / el -form -item > < / td > < / tr >
< tr > < td > < el-form-item label = "营业执照企业名称" prop = "legalName" required > < el-input v-model = "form.legalName" placeholder="请输入营业执照上的企业名称" maxlength="128" /></el-form-item></td><td><el-form-item label="法定代表人" prop="legalRepresentative"><el-input v-model="form.legalRepresentative" placeholder="请输入法定代表人姓名(选填)" maxlength="64" / > < / el-form-item > < / td > < / tr >
< tr > < td > < el-form-item label = "企业类型 " prop = "enter pri seTyp e" > < el-input v-model = "form.enterpriseType" placeholder="如:有限责任公司" maxlength="64" /></el-form-item></td><td><el-form-item label="成立日期" prop="establishedDate"><el-date-picker style="width: 100%;" v-model="form.establishedDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择成立日期" :disabled-date="(date: Date) => date.getTime() > Date.now()" / > < / el-form-item > < / td > < / tr >
< tr > < td colspan = "2" > < el-form-item label = "注册资本 " prop = "registeredCapital" > < div class = "capital-input "> < el-input-number v-model = "form.registeredCapital" :min="0" :precision="2" controls-position="right" placeholder="请输入注册资本(选填)" /><el-select v-model="form.registeredCapitalCurrency" aria-label="注册资本币种"><el-option label="人民币 CNY" value="CNY" /><el-option label="美元 USD" value="USD" /><el-option label="港币 HKD" value="HKD" / > < / el-select > < / div > < / el-form-item > < / td > < / tr >
< tr > < td colspan = "2" > < el-form-item label = "注册地址 " prop = "registeredAddress" > < el-input v-model = "form.registeredAddress" placeholder="请输入省市区、街道及门牌信息" maxlength="500" / > < / el-form-item > < / td > < / tr >
< / tbody > < / table >
< / section >
< FormSection title = "联系与商务" description = "维护客户联系人、账期和内部跟进信息。" > < div class = "form-grid three-cols" >
< el-form-item label = "联系人" prop = "contactName" required > < el-input v-model = "form.contactName" placeholder="请输入主要联系人姓名" maxlength="64" / > < / el-form-item >
< el-form-item label = "公司联系电话 " prop = "contactPhon e" required > < el-input v-model = "form.contactPhon e" placeholder="请输入公司联系电话 " maxlength="32 " /><small class="field-hint" > 仅用于企业业务联系 , 不会修改企业管理员登录手机号 ; 登录手机号请在成员 / 账号管理中单独维护 , 修改会影响登录 。 < / small > < / el -form -item >
< el-form-item label = "联系邮箱" prop = "contactEmail" > < el-input v-model = "form.contactEmail" placeholder="如:contact@example.com" maxlength="128" / > < / el-form-item >
< el-form-item label = "联系微信 " prop = "contactWechat " > < el-input v-model = "form.contactWechat " placeholder="请输入联系人微信号 " maxlength="64" / > < / el-form-item >
<!-- < el-form-item label = "客户套餐 " prop = "servicePackage" required > < el-select v-model = "form.servicePackage"><el-option label="基础版" value="基础版" /><el-option label="专业版" value="专业版" /><el-option label="企业版" value="企业版 " / > < / el-select > < / el-form-item > -- >
< el-form-item label = "销售人员" prop = "salesOwnerName" > < el-input v-model = "form.salesOwnerName" placeholder="请输入负责销售人员" maxlength="64" / > < / el-form-item >
< el-form-item label = "账期(天)" prop = "paymentTermDays" required > < el-input-number v-model = "form.paymentTermDays" :min="0" :max="365" controls-position="right" placeholder="请输入账期天数" / > < / el-form-item >
< / div > < el-form-item label = "内部备注" prop = "internalRemark" > < el-input v-model = "form.internalRemark" type="textarea" :rows="3" placeholder="请输入仅平台人员可见的客户备注" maxlength="1000" show -word -limit / > < / el-form-item > < / FormSection >
< section class = "density-dialog-section" data -tone = " work " > < h3 class = "density-dialog-section__title" > 联系与商务 < / h3 > < p class = "density-dialog-note" > 仅用于企业业务联系 , 不会修改企业管理员登录手机号 ; 登录手机号请在成员 / 账号管理中单独维护 , 修改会影响登录 。 < / p >
< table class = "density-table density-table--editable customer-editor-table" aria -label = " 联系与商务 " > < tbody >
< tr > < td > < el-form-item label = "联系人 " prop = "contactNam e" required > < el-input v-model = "form.contactNam e" placeholder="请输入主要联系人姓名 " maxlength="64 " /></el-form-item></td><td><el-form-item label="公司联系电话" prop="contactPhone" required><el-input v-model="form.contactPhone" placeholder="请输入公司联系电话" maxlength="32" / > < / el-form-item > < / td > < / tr >
< tr > < td > < el-form-item label = "联系邮箱" prop = "contactEmail" > < el-input v-model = "form.contactEmail" placeholder="如:contact@example.com" maxlength="128" /></el-form-item></td><td><el-form-item label="联系微信" prop="contactWechat"><el-input v-model="form.contactWechat" placeholder="请输入联系人微信号" maxlength="64" / > < / el-form-item > < / td > < / tr >
< tr > < td > < el-form-item label = "销售人员 " prop = "salesOwnerName " > < el-input v-model = "form.salesOwnerName " placeholder="请输入负责销售人员 " maxlength="64" /></el-form-item></td><td><el-form-item label="账期(天)" prop="paymentTermDays" required><el-input-number v-model="form.paymentTermDays" :min="0" :max="365" controls-position="right" placeholder="请输入账期天数" / > < / el-form-item > < / td > < / tr >
< tr > < td colspan = "2" > < el-form-item label = "内部备注 " prop = "internalRemark" > < el-input v-model = "form.internalRemark" type="textarea" :rows="3" placeholder="请输入仅平台人员可见的客户备注" maxlength="1000 " show -word -limit / > < / el-form-item > < / td > < / tr >
< / tbody > < / table >
< / section >
< FormSection class = "qualification-section" title = "企业认证资料" description = " 营业执照和18位统一社会信用代码为必填业务项;法人身份证、 货代资质证书和银行开户许可证均为选填。" > < div v-if = "editorStatus" class="qualification-heading"><el-tag :type="statusMeta(editorStatus).type" effect="light" > {{ statusMeta ( editorStatus ) .label }} < / el -tag > < / div >
< el-form-item label = "统一社会信用代码" prop = "unifiedSocialCreditCode" required class = "qualification-credit-code" > < el-input v-model = "form.unifiedSocialCreditCode" :disabled="creditCodeEstablished " placeholder="请输入18位大写字母或数字" maxlength="18" @input="form.unifiedSocialCreditCode = String($event).toUpperCase()" / > < small > { { creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照共同保存;法人身份证为选填。' } } < / small > < / el-form-item >
< section class = "density-dialog-section qualification-section" data -tone = " work " > < h3 class = "density-dialog-section__title" > 企业认证资料 < / h3 > < p class = "density-dialog-note" > 营业执照和18位统一社会信用代码为必填业务项 ; 法人身份证 、 货代资质证书和银行开户许可证均为选填 。 < / p > < div v-if = "editorStatus" class="qualification-heading"><el-tag :type="statusMeta(editorStatus).type" effect="light" > {{ statusMeta ( editorStatus ) .label }} < / el -tag > < / div >
< table class = "density-table density-table--editable" aria -label = " 企业信用代码 " > < tbody > < tr > < td > < el-form-item label = "统一社会信用代码" prop = "unifiedSocialCreditCode" required class = "qualification-credit-code" > < span v-if = "creditCodeEstablished">{{ form.unifiedSocialCreditCode }}</span><el-input v-else v-model="form.unifiedSocialCreditCode " placeholder="请输入18位大写字母或数字" maxlength="18" @input="form.unifiedSocialCreditCode = String($event).toUpperCase()" / > < small > { { creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照共同保存;法人身份证为选填。' } } < / small > < / el-form-item > < / td > < / tr > < / tbody > < / table >
< div v-if = "qualificationLoadError" class="qualification-load-error" role="alert">{{ qualificationLoadError }}<el-button v-if="editingId" link type="primary" @click="loadEditorQualifications(editingId)" > 重试 < / el -button > < / div >
< div class = "qualification-table" role = "table" aria -label = " 客户资质文件 " >
< div class = "qualification-row qualification-header" role = "row" > < span role = "columnheader" > 文件类型 < / span > < span role = "columnheader" > 上传文件 < / span > < span role = "columnheader" > 有效期 < / span > < / div >
@@ -1122,96 +1122,90 @@ onMounted(() => { void loadCustomers() })
< / div >
< / div >
< p class = "qualification-hint" > 支持 PDF 、 JPG 、 PNG , 单个文件不超过 5 MB ; < span > * < / span > 为提交审核必填项 。 < / p >
< / FormSection >
< / section >
< / el-form >
< template # footer > < div class = "customer-editor-footer" > < span > 保存资料时会一并上传已选资质 ; 进入审核仍需点击 “ 提交审核 ” 并再次确认 < / span > < div > < el-button : disabled = "editorLoading || saving || submitting || uploadingQualifications" @click ="editorOpen = false" > 关闭 < / el -button > < el-button :loading = "saving" : disabled = "editorLoading || submitting || uploadingQualifications" @click ="saveCustomer" > 保存资料和资质 < / el -button > < el-button v-if = "canSubmitReview" type="primary" :loading="submitting" :disabled="editorLoading || saving || uploadingQualifications" @click="submitReview" > 提交审核 < / el -button > < / div > < / div > < / template >
< template # footer > < div class = "customer-editor-footer" > < span > 保存资料时会一并上传已选资质 ; 进入审核仍需点击 “ 提交审核 ” 并再次确认 < / span > < div class = "density-actions density-actions--footer" > < el-button : disabled = "editorLoading || saving || submitting || uploadingQualifications" @click ="editorOpen = false" > 关闭 < / el -button > < el-button :loading = "saving" : disabled = "editorLoading || submitting || uploadingQualifications" @click ="saveCustomer" > 保存资料和资质 < / el -button > < el-button v-if = "canSubmitReview" type="primary" :loading="submitting" :disabled="editorLoading || saving || uploadingQualifications" @click="submitReview" > 提交审核 < / el -button > < / div > < / div > < / template >
< / AppDialog >
< AppDialog v-model = "qualificationPreviewOpen" :title="qualificationPreviewName || '查看资质文件' " width="min(860px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog qualification-preview-dialog" destroy-on-close :show-confirm="false" cancel-text="关闭预览" >
< div class = "qualification-preview" >
< AppDialog v-model = "qualificationPreviewOpen" :title="customerDialogTitle('查看资质文件', editorCustomer) " width="min(860px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog qualification-preview-dialog customer-qualification-preview" fullscreenable destroy-on-close :show-confirm="false" cancel-text="关闭预览" >
< p class = "density-dialog-note customer-preview-filename" > { { qualificationPreviewName } } < / p >
< div class = "density-dialog-section qualification-preview" data -tone = " source " >
< img v-if = "qualificationPreviewUrl && qualificationPreviewKind === 'image'" :src="qualificationPreviewUrl" :alt="`${qualificationPreviewName}预览`" / >
< iframe v-else-if = "qualificationPreviewUrl && qualificationPreviewKind === 'pdf'" :src="qualificationPreviewUrl" :title="`${qualificationPreviewName} PDF 预览`" / >
< / div >
< / AppDialog >
< el-dialog v-model = "approvalResultOpen" :title="credentialSource === 'creation' ? '客户已创建,管理员账号已生成' : '审核通过,管理员账号已创建'" width="min(560px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog approval-result-dialog" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" >
< div v-if = "approvalResult" class="approval-result-content " >
< AppDialog v-model = "approvalResultOpen" :title="customerDialogTitle( credentialSource === 'creation' ? '客户已创建,管理员账号已生成' : '审核通过,管理员账号已创建', credentialSource === 'creation' ? editorCustomer : selectedReview) " width="min(560px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog approval-result-dialog" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="false" >
< div v-if = "approvalResult" class="density-dialog-sections " >
< div class = "approval-result-warning" role = "alert" > < ShieldCheck :size = "20" / > < div > < strong > 请立即保存以下一次性凭证 < / strong > < span > 临时密码只在本次 { { credentialSource === 'creation' ? '创建' : '审核' } } 响应中返回 , 关闭后无法再次查看 。 < / span > < / div > < / div >
< dl class = "approval-credentials" >
< div > < dt > 登录账号 < / dt > < dd > < code > { { approvalResult . administratorUsername || '未返回' } } < / code > < CopyButton v-if = "approvalResult.administratorUsername" :value="approvalResult.administratorUsername" label="登录账号" size="small" / > < / dd > < / div >
< div > < dt > 临时密码 < / dt > < dd > < code > { { approvalResult . temporaryPassword || '未返回' } } < / code > < CopyButton v-if = "approvalResult.temporaryPassword" :value="approvalResult.temporaryPassword" label="临时密码" size="small" / > < / dd > < / div >
< / dl >
< section class = "density-dialog-section" data -tone = " summary " > < table class = "density-table" aria -label = " 一次性管理员凭据 " > < tbody >
< tr > < th scope = "row" > 登录账号 < / th > < td > < div class = "customer-credential-value" > < code > { { approvalResult . administratorUsername || '未返回' } } < / code > < CopyButton v-if = "approvalResult.administratorUsername" :value="approvalResult.administratorUsername" label="登录账号" size="small" / > < / div > < / td > < / tr >
< tr > < th scope = "row" > 临时密码 < / th > < td > < div class = "customer-credential-value" > < code > { { approvalResult . temporaryPassword || '未返回' } } < / code > < CopyButton v-if = "approvalResult.temporaryPassword" :value="approvalResult.temporaryPassword" label="临时密码" size="small" / > < / div > < / td > < / tr >
< / tbody > < / table > < / section >
< / div >
< template # footer > < el-button type = "primary" @click ="closeApprovalResult" > 我已保存 , 关闭 < / el -button > < / template >
< / el-dialog >
< / AppDialog >
< el-dialog v-model = "decisionOpen" title="客户入驻审核 " width="min(1040px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog review-decision-dialog" :close-on-click-modal="false" >
< div v-if = "selectedReview" class="review -dialog-content " >
< header class = "review -dialog-header" >
< div class = "review -dialog-title" > < span class = "review-dialog-icon" > < Building2 :size = "18" / > < / span > < div > < strong > { { selectedReview . tenantName || '未命名客户' } } < / strong > < span > 客户编号 : { { selectedReview . tenantCode || '未生成' } } < / span > < / div > < / div >
< el-tag type = "warning" effect = "plain" > 待审核 < / el-tag >
< / header >
< div class = "review-dialog-grid" >
< section class = "review-dialog-section review-dialog-card" >
< div class = "review-section-heading" > < div > < h3 > 企业基本信息 < / h3 > < span > 核对客户提交的主体资料 < / span > < / div > < CircleCheck :size = "18" / > < / div >
< dl class = "review-info-grid density-fields" >
< div > < dt > 客户名称 < / dt > < dd > { { selectedReview . tenantName || '未填写' } } < / dd > < / div >
< div > < dt > 客户编号 < / dt > < dd > { { selectedReview . tenantCode || '未生成' } } < / dd > < / div >
< div > < dt > 联系人 < / dt > < dd > { { selectedReview . contactName || '未填写' } } < / dd > < / div >
< div > < dt > 公司联系电话 < / dt > < dd > { { selectedReview . contactPhone || '未填写' } } < / dd > < / div >
< div > < dt > 联系邮箱 < / dt > < dd > { { selectedReview . contactEmail || '未填写' } } < / dd > < / div >
< div > < dt > 提交时间 < / dt > < dd > { { formatDate ( selectedReview . submittedAt || selectedReview . createdAt ) } } < / dd > < / div >
< / dl >
< / section >
< section class = "review-dialog-section review-dialog-card" >
< div class = "review-section-heading" > < div > < h3 > 资质文件 < / h3 > < span > 营业执照为必填 ; 法人身份证及其他资质为选填 。 共 { { reviewQualifications . length } } 份 < / span > < / div > < FileCheck2 :size = "18" / > < / div >
< div v-if = "reviewQualificationsLoading" class="review-document-state" > 正在加载资质文件... < / div >
< div v-else-if = "reviewQualificationsError" class="review-document-error" role="alert"><span>{{ reviewQualificationsError.message }}</span><el-button link type="primary" @click="loadReviewQualifica tions "> 重试 < / el -button > < / div >
< div v-else class = "review-document-table" role = "table" aria -label = " 待审核客户资质 " >
< div class = "review-document-header" role = "row" > < span role = "columnheader" > 资质类型 < / span > < span role = "columnheader" > 已上传文件 < / span > < span role = "columnheader" > 操作 < / span > < / div >
< div v-for = "item in reviewQualifications" :key="String(item.id)" class="review-document-row" role="row"><span role="cell" class="review-document-type">{{ qualificationLabel(item.qualificationType) }}</span><span role="cell" class="review-document-name" :title="qualificationName(item)"><FileText :size="15" />{{ qualificationName(item) }}</span><span role="cell" class="review-document-actions"><el-button link type="primary" :loading="reviewDocumentLoading === String(item.id)" @click="loadReviewDocument(item, false)">预览</el-button><el-button link :disabled="Boolean(reviewDocumentLoading)" @click="loadReviewDocument(item, true)" > 下载 < / el -button > < / span > < / div >
< div v-if = "reviewQualifications.length === 0" class="review-document-empty"><FileText :size="18" / > < span > 暂无资质文件 < / span > < / div >
< / div >
< / section >
< / div >
< section class = "review-decision-card" >
< div class = "review-section-heading" > < div > < h3 > 审核决定 < / h3 > < span > 确认资料无误后提交审核结果 < / span > < / div > < ShieldCheck :size = "18" / > < / div >
< AppDialog v-model = "decisionOpen" : title="customerDialogTitle('客户入驻审核', selectedReview)" fullscreenable :confirm-loading="reviewSubmitting" :lock-while-submitting="false " width="min(1040px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog review-decision-dialog" :close-on-click-modal="false" >
< div v-if = "selectedReview" class="density -dialog-sections " >
< section class = "density -dialog-section" data -tone = " source " >
< h3 class = "density -dialog-section__ title" > 企业基本信息 < el-tag type = "warning" effect = "plain" > 待审核 < / el-tag > < / h3 >
< table class = "density-table customer-review-table" aria -label = " 待审核企业资料 " > < tbody >
< tr > < th scope = "row" > 客户名称 < / th > < td > { { selectedReview . tenantName || '未填写' } } < / td > < th scope = "row" > 客户编号 < / th > < td > { { selectedReview . tenantCode || '未生成' } } < / td > < / tr >
< tr > < th scope = "row" > 联系人 < / th > < td > { { selectedReview . contactName || '未填写' } } < / td > < th scope = "row" > 公司联系电话 < / th > < td > { { selectedReview . contactPhone || '未填写' } } < / td > < / tr >
< tr > < th scope = "row" > 联系邮箱 < / th > < td > { { selectedReview . contactEmail || '未填写' } } < / td > < th scope = "row" > 提交时间 < / th > < td > { { formatDate ( selectedReview . submittedAt || selectedReview . createdAt ) } } < / td > < / tr >
< / tbody > < / table >
< / section >
< section class = "density-dialog-section" data -tone = " source " >
< h3 class = "density-dialog-section__title" > 资质文件 < / h3 >
< p class = "density-dialog-note" > 营业执照为必填 ; 法人身份证及其他资质为选填 。 共 { { reviewQualifications . length } } 份 < / p >
< div v-if = "reviewQualificationsLoading" class="review-document-state" > 正在加载资质文件... < / div >
< div v-else-if = "reviewQualificationsError" class="review-document-error" role="alert"><span>{{ reviewQualificationsError.message }}</span><el-button link type="primary" @click="loadReviewQualifications" > 重试 < / el -button > < / div >
< table v-else class = "density-table customer-review-documents" aria -label = " 待审核客户资质 " >
< thead > < tr > < th > 资质类型 < / th > < th > 已上传文件 < / th > < th > 操作 < / th > < / tr > < / thead >
< tbody > < tr v-for = "item in reviewQualifications" :key="String(item.id)"><td>{{ qualificationLabel(item.qualificationType) }}</td><td :title="qualificationName(item)">{{ qualificationName(item) }}</td><td><div class="review-document-actions density-actions"><el-button class="density-action" data-tone="primary" :loading="reviewDocumentLoading === String(item.id)" @click="loadReviewDocument(item, false)">预览</el-button><el-button class="density-action" :disabled="Boolean(reviewDocumentLoading)" @click="loadReviewDocument(item, true)" > 下载 < / el -button > < / div > < / td > < / tr >
< tr v-if = "reviewQualifications.length === 0"><td colspan="3" > 暂无资质文件 < / td > < / tr > < / tbody >
< / table >
< / section >
< section class = "density-dialog-sec tion" data -tone = " work " >
< h3 class = "density-dialog-section__title" > 审核决定 < / h3 >
< el-radio-group v-if = "access.can('platform:customer:approve') && access.can('platform:customer:reject')" v-model="decision.type" class="decision-type" aria-label="审核决定"><el-radio-button label="approve">通过</el-radio-button><el-radio-button label="reject" > 驳回 < / el -radio -button > < / el-radio-group >
< el-form ref = "decisionFormRef" :model = "decision" :rules = "decisionRules" label -position = " top " >
< el-form ref = "decisionFormRef" :model = "decision" :rules = "decisionRules" label -position = " top " class = "density-form density-form--flush-end" >
< el-form-item prop = "comment" : label = "decision.type === 'approve' ? '审核意见' : '驳回原因'" required > < el-input v-model = "decision.comment" type="textarea" :rows="3" maxlength="300" show-word-limit :placeholder="decision.type === 'approve' ? '可填写审核意见' : '请明确说明需要企业修改的内容'" / > < / el-form-item >
< / el-form >
< / section >
< / div >
< template # footer > < div class = "drawer -footer" > < el-button :disabled = "reviewSubmitting" @click ="decisionOpen = false" > 取消 < / el -button > < el-button v-if = "access.can('platform:customer:reject') && decision.type === 'reject'" type="danger" :loading="reviewSubmitting" @click="submitDecision">确认驳回</el-button><el-button v-if="access.can('platform:customer:approve') && decision.type === 'approve'" type="primary" :loading="reviewSubmitting" @click="submitDecision" > 审核通过 < / el -button > < / div > < / template >
< / el-dialog >
< AppDialog class = "admin-density-dialog customer-density-dialog" v-model = "reviewPreviewOpen" :title="reviewPreviewName || '查看资质文件'" width="min(860px, calc(100vw - 28px))" destroy-on-close :show-confirm="false" cancel-text="关闭预览" @closed="clearReviewPreview" >
< div class = "qualification-preview" > < img v-if = "reviewPreviewUrl && reviewPreviewKind === 'image'" :src="reviewPreviewUrl" :alt="`${reviewPreviewName}预览`" /><iframe v-else-if="reviewPreviewUrl" :src="reviewPreviewUrl" :title="`${reviewPreviewName} PDF 预览`" / > < / div >
< template # footer > < div class = "density-actions density-actions- -footer" > < el-button :disabled = "reviewSubmitting" @click ="decisionOpen = false" > 取消 < / el -button > < el-button v-if = "access.can('platform:customer:reject') && decision.type === 'reject'" type="danger" :loading="reviewSubmitting" @click="submitDecision">确认驳回</el-button><el-button v-if="access.can('platform:customer:approve') && decision.type === 'approve'" type="primary" :loading="reviewSubmitting" @click="submitDecision" > 审核通过 < / el -button > < / div > < / template >
< / AppDialog >
< el-dialog v-model = "passwordDialog Open" title="重置客户密码" width="min(52 0px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog customer-password-dialog" :close-on-click-modal="false" :close-on-press-escape="!passwordSubmitting" :show- close="!passwordSubmitting " >
< AppDialog class = "admin-density-dialog customer-density-dialog customer-qualification-preview" v-model = "reviewPreview Open" : title="customerDialogTitle('查看审核资质', selectedReview)" fullscreenable width="min(86 0px, calc(100vw - 28px))" destroy-on-close :show-confirm="false" cancel-text="关闭预览" @ closed ="clearReviewPreview ">
< p class = "density-dialog-note customer-preview-filename" > { { reviewPreviewName } } < / p >
< div class = "density-dialog-section qualification-preview" data -tone = " source " > < img v-if = "reviewPreviewUrl && reviewPreviewKind === 'image'" :src="reviewPreviewUrl" :alt="`${reviewPreviewName}预览`" /><iframe v-else-if="reviewPreviewUrl" :src="reviewPreviewUrl" :title="`${reviewPreviewName} PDF 预览`" / > < / div >
< / AppDialog >
< AppDialog v-model = "passwordDialogOpen" :title="customerDialogTitle('重置客户密码', passwordTenant)" :confirm-loading="passwordSubmitting" width="min(520px, calc(100vw - 28px))" class="admin-density-dialog customer-density-dialog customer-password-dialog" :close-on-click-modal="false" :close-on-press-escape="!passwordSubmitting" :show-close="!passwordSubmitting" >
< div v-if = "passwordTenant" class="decision-summary" > < div > < strong > 正在重置企业管理员登录密码 < / strong > < span > { { passwordTenant . tenantName } } < / span > < / div > < span > 客户编号 : { { passwordTenant . tenantCode } } < / span > < / div >
< el-form ref = "passwordFormRef" :model = "passwordForm" :rules = "passwordRules" label -position = " top " >
< el-form ref = "passwordFormRef" :model = "passwordForm" :rules = "passwordRules" label -position = " top " class = "density-form" >
< el-form-item label = "新密码" prop = "newPassword" required > < div class = "password-input-row" > < el-input v-model = "passwordForm.newPassword" type="password" show-password autocomplete="new-password" placeholder="请输入 8-20 位新密码" /><el-button @click="randomPassword" > 随机生成 < / el -button > < / div > < / el-form-item >
< el-form-item label = "确认新密码" prop = "confirmPassword" required > < el-input v-model = "passwordForm.confirmPassword" type="password" show-password autocomplete="new-password" placeholder="请再次输入新密码" / > < / el-form-item >
< el-checkbox v-model = "passwordForm.forceChangePassword" > 客户下次登录时必须修改密码 < / el -checkbox >
< p class = "dialog-hint " > 重置对象为企业 OWNER 管理员 , 原密码将立即失效 。 < / p >
< p class = "density- dialog-note " > 重置对象为企业 OWNER 管理员 , 原密码将立即失效 。 < / p >
< / el-form >
< template # footer > < div class = "drawer -footer" > < el-button :disabled = "passwordSubmitting" @click ="passwordDialogOpen = false" > 取消 < / el -button > < el-button type = "primary" :loading = "passwordSubmitting" @click ="submitResetPassword" > 确认重置 < / el -button > < / div > < / template >
< / el-dialog >
< template # footer > < div class = "density-actions density-actions- -footer" > < el-button :disabled = "passwordSubmitting" @click ="passwordDialogOpen = false" > 取消 < / el -button > < el-button type = "primary" :loading = "passwordSubmitting" @click ="submitResetPassword" > 确认重置 < / el -button > < / div > < / template >
< / AppDialog >
< el-dialog class = "admin-density-dialog customer-density-dialog" v-model = "impersonationDialogOpen" title="跳转登录客户工作台 " width="min(560px, calc(100vw - 28px))" :close-on-click-modal="false" :close-on-press-escape="!impersonationSubmitting" :show-close="!impersonationSubmitting" >
< AppDialog class = "admin-density-dialog customer-density-dialog" v-model = "impersonationDialogOpen" : title="customerDialogTitle('跳转登录客户工作台', impersonationTenant)" :confirm-loading="impersonationSubmitting " width="min(560px, calc(100vw - 28px))" :close-on-click-modal="false" :close-on-press-escape="!impersonationSubmitting" :show-close="!impersonationSubmitting" >
< div v-if = "impersonationTenant" class="decision-summary" > < div > < strong > { { impersonationTenant . tenantName } } < / strong > < span > 将以企业管理员身份打开客户工作台 < / span > < / div > < span > 客户编号 : { { impersonationTenant . tenantCode } } < / span > < / div >
< el-alert type = "warning" :closable = "false" show -icon > 本操作会生成一次性 、 短时有效的客户会话 , 并记录代登录原因 ; 不会替换当前 Admin 会话 。 < / el-alert >
< el-form ref = "impersonationFormRef" :model = "impersonationForm" :rules = "impersonationRules" label -position = " top " > < el-form-item label = "代登录原因" prop = "reason" required > < el-input v-model = "impersonationForm.reason" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请填写 5-500 字的业务原因" / > < / el-form-item > < / el-form >
< template # footer > < div class = "drawer -footer" > < el-button :disabled = "impersonationSubmitting" @click ="impersonationDialogOpen = false" > 取消 < / el -button > < el-button type = "primary" :loading = "impersonationSubmitting" @click ="submitImpersonation" > 确认跳转 < / el -button > < / div > < / template >
< / el-dialog >
< el-form ref = "impersonationFormRef" :model = "impersonationForm" :rules = "impersonationRules" label -position = " top " class = "density-form" > < el-form-item label = "代登录原因" prop = "reason" required > < el-input v-model = "impersonationForm.reason" type="textarea" :rows="4" maxlength="500" show-word-limit placeholder="请填写 5-500 字的业务原因" / > < / el-form-item > < / el-form >
< template # footer > < div class = "density-actions density-actions- -footer" > < el-button :disabled = "impersonationSubmitting" @click ="impersonationDialogOpen = false" > 取消 < / el -button > < el-button type = "primary" :loading = "impersonationSubmitting" @click ="submitImpersonation" > 确认跳转 < / el -button > < / div > < / template >
< / AppDialog >
< el-dialog class = "admin-density-dialog customer-density-dialog" v-model = "archiveDialogOpen" title="删除客户 " width="min(560px, calc(100vw - 28px))" :close-on-click-modal="false" :close-on-press-escape="!archiveSubmitting" :show-close="!archiveSubmitting" >
< AppDialog class = "admin-density-dialog customer-density-dialog" v-model = "archiveDialogOpen" : title="customerDialogTitle('删除客户', archiveTenant)" :confirm-loading="archiveSubmitting " width="min(560px, calc(100vw - 28px))" :close-on-click-modal="false" :close-on-press-escape="!archiveSubmitting" :show-close="!archiveSubmitting" >
< el -alert type = "warning" :closable = "false" show -icon > 删除是逻辑归档 : 历史业务数据会保留 , 客户账号会被停用 。 是否允许归档由服务端最终判断 。 < / el-alert >
< div v-if = "archiveTenant" class="archive-confirm"><strong>{{ archiveTenant.tenantName }}</strong><span>请输入客户编号 {{ archiveTenant.tenantCode }} 以确认。</span><el-input v-model="archiveConfirmCode" :disabled="archiveSubmitting" :placeholder="archiveTenant.tenantCode" autocomplete="off" / > < / div >
< template # footer > < div class = "drawer -footer" > < el-button :disabled = "archiveSubmitting" @click ="archiveDialogOpen = false" > 取消 < / el -button > < el-button type = "danger" :loading = "archiveSubmitting" : disabled = "!archiveTenant || archiveConfirmCode.trim() !== archiveTenant.tenantCode" @click ="submitArchive" > 确认删除 < / el -button > < / div > < / template >
< / el-dialog >
< template # footer > < div class = "density-actions density-actions- -footer" > < el-button :disabled = "archiveSubmitting" @click ="archiveDialogOpen = false" > 取消 < / el -button > < el-button type = "danger" :loading = "archiveSubmitting" : disabled = "!archiveTenant || archiveConfirmCode.trim() !== archiveTenant.tenantCode" @click ="submitArchive" > 确认删除 < / el -button > < / div > < / template >
< / AppDialog >
< / AdminPage >
< / template >
@@ -1227,7 +1221,7 @@ onMounted(() => { void loadCustomers() })
: deep ( . customer - list - table . customer - code - column . cell ) { white - space : nowrap ; }
. capital - input { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) 130 px ; width : 100 % ; gap : var ( -- admin - space - 3 , 8 px ) ; } . capital - input : deep ( . el - input - number ) { width : 100 % ; } . capital - input : deep ( . el - select ) { width : 100 % ; }
. qualification - heading { display : flex ; justify - content : flex - end ; margin - bottom : var ( -- admin - space - 5 , 12 px ) ; } . qualification - hint { margin : var ( -- admin - space - 5 , 12 px ) 0 0 ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; line - height : 1.6 ; } . qualification - hint span { color : var ( -- el - color - danger ) ; } . qualification - label > span { color : var ( -- el - color - danger ) ; }
. qualification - credit - code { max - width : 420 px ; margin - top : var ( -- admin - space - 6 , 16 px ) ; } . qualification - credit - code small { display : block ; margin - top : var ( -- admin - space - 2 , 6 px ) ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 11 px ) ; line - height : 1.5 ; } . field - hint { display : block ; margin - top : var ( -- admin - space - 2 , 6 px ) ; color : var ( -- cc - muted ) ; line - height : 1.5 ; }
. qualification - credit - code small { display : block ; margin - top : var ( -- admin - space - 2 , 6 px ) ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 11 px ) ; line - height : 1.5 ; } . field - hint { display : block ; margin - top : var ( -- admin - space - 2 , 6 px ) ; color : var ( -- cc - muted ) ; line - height : 1.5 ; }
. qualification - table { container - type : inline - size ; overflow : hidden ; margin - top : 14 px ; border : 1 px solid var ( -- cc - border ) ; border - radius : 8 px ; } . qualification - row { display : grid ; grid - template - columns : 170 px minmax ( 0 , 1 fr ) 220 px ; min - width : 0 ; border - top : 1 px solid var ( -- cc - border ) ; } . qualification - row : first - child { border - top : 0 ; } . qualification - row > * { display : flex ; min - width : 0 ; align - items : center ; } . qualification - header { min - height : 44 px ; background : # f7f8fc ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; font - weight : 600 ; }
. qualification - label { gap : 5 px ; font - weight : 600 ; } . qualification - label svg { margin - left : auto ; color : var ( -- el - color - success ) ; }
. qualification - optional { padding : 2 px 5 px ; border - radius : var ( -- admin - radius , 4 px ) ; background : var ( -- el - fill - color - light ) ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 10 px ) ; font - weight : 500 ; line - height : 1.4 ; }
@@ -1246,51 +1240,17 @@ onMounted(() => { void loadCustomers() })
. qualification - preview - placeholder { display : grid ; place - items : center ; gap : 2 px ; }
. qualification - preview - placeholder small { font - size : var ( -- admin - font - caption , 10 px ) ; font - weight : 700 ; }
. qualification - preview - action { position : absolute ; inset : auto 0 0 ; display : flex ; min - height : 22 px ; align - items : center ; justify - content : center ; gap : 3 px ; background : rgba ( 23 , 32 , 51 , .78 ) ; color : var ( -- admin - surface , # fff ) ; }
. qualification - preview { display : grid ; max - height : min ( 70 vh , 760 px ) ; min - height : 240 px ; place - items : center ; overflow : auto ; border : 1 px solid var ( -- cc - border ) ; border - radius : 6 px ; background : var ( -- admin - label - bg , # f8fafc ) ; }
. qualification - preview img { display : block ; max - width : 100 % ; height : auto ; }
. qualification - preview iframe { width : 100 % ; min - height : min ( 68 vh , 720 px ) ; border : 0 ; background : var ( -- admin - surface , # fff ) ; }
. qualification - load - error { display : flex ; align - items : center ; justify - content : space - between ; gap : var ( -- admin - space - 4 , 10 px ) ; margin - top : var ( -- admin - space - 5 , 12 px ) ; padding : var ( -- admin - space - 4 , 10 px ) var ( -- admin - space - 5 , 12 px ) ; border : 1 px solid # fecdca ; border - radius : 6 px ; background : # fffafa ; color : # b42318 ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. customer - editor - footer { display : flex ; align - items : center ; justify - content : space - between ; gap : var ( -- admin - space - 6 , 16 px ) ; } . customer - editor - footer > span { color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; text - align : left ; } . customer - editor - footer > div { display : flex ; flex : 0 0 auto ; gap : var ( -- admin - space - 3 , 8 px ) ; } . customer - editor - footer : deep ( . el - button + . el - button ) { margin - left : 0 ; }
. customer - editor - footer { display : flex ; align - items : center ; justify - content : space - between ; gap : var ( -- admin - space - 6 , 16 px ) ; } . customer - editor - footer > span { color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; text - align : left ; }
. decision - summary { display : flex ; min - width : 0 ; align - items : center ; justify - content : space - between ; gap : var ( -- admin - space - 5 , 12 px ) ; background : var ( -- admin - label - bg , # f8fafc ) ; } . decision - summary > * { min - width : 0 ; overflow - wrap : anywhere ; } . decision - summary strong { color : var ( -- cc - text ) ; font - size : var ( -- admin - font - body , 13 px ) ; } . decision - summary span { color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 11 px ) ; }
. review - dialog - content { display : grid ; }
. review - dialog - header { display : flex ; align - items : center ; justify - content : space - between ; gap : var ( -- admin - space - 6 , 16 px ) ; border : 1 px solid # dbe4f0 ; }
. review - dialog - title { display : flex ; min - width : 0 ; align - items : center ; gap : 11 px ; }
. review - dialog - title > div { display : grid ; min - width : 0 ; gap : 3 px ; }
. review - dialog - title strong { overflow : hidden ; color : # 18243 a ; font - size : var ( -- admin - font - title , 16 px ) ; text - overflow : ellipsis ; }
. review - dialog - title span : not ( . review - dialog - icon ) { overflow : hidden ; color : # 718096 ; font - size : var ( -- admin - font - caption , 12 px ) ; text - overflow : ellipsis ; white - space : nowrap ; }
. review - dialog - icon { width : 36 px ; height : var ( -- admin - row - height , 36 px ) ; flex : 0 0 36 px ; place - items : center ; border - radius : 9 px ; background : # e9efff ; color : # 526 fe8 ; }
. review - dialog - grid { display : grid ; grid - template - columns : minmax ( 270 px , .85 fr ) minmax ( 390 px , 1.15 fr ) ; }
. review - dialog - section { min - width : 0 ; border : 1 px solid # e5eaf1 ; background : var ( -- admin - surface , # fff ) ; }
. review - decision - card { min - width : 0 ; border : 1 px solid # e5eaf1 ; background : var ( -- admin - surface , # fff ) ; }
. review - section - heading { display : flex ; align - items : flex - start ; justify - content : space - between ; gap : var ( -- admin - space - 5 , 12 px ) ; }
. review - section - heading > div { min - width : 0 ; }
. review - section - heading h3 { margin : 0 ; color : # 1 a2438 ; font - size : var ( -- admin - font - section , 14 px ) ; font - weight : 700 ; }
. review - section - heading span { display : block ; margin - top : var ( -- admin - space - 1 , 4 px ) ; color : # 8 a94a6 ; line - height : 1.4 ; }
. review - section - heading > svg { flex : 0 0 auto ; color : # 657 bea ; }
. review - info - grid { margin : 0 ; }
. review - document - table { overflow : hidden ; border : 1 px solid # e4e9f1 ; }
. review - document - header { display : grid ; grid - template - columns : 118 px minmax ( 0 , 1 fr ) 138 px ; }
. review - document - row { display : grid ; grid - template - columns : 118 px minmax ( 0 , 1 fr ) 138 px ; }
. review - document - header { background : # f5f7fb ; color : # 657188 ; font - weight : 700 ; }
. review - document - header > span { min - width : 0 ; padding : 9 px var ( -- admin - space - 4 , 10 px ) ; overflow - wrap : anywhere ; border - right : 1 px solid # e4e9f1 ; }
. review - document - row > span { min - width : 0 ; padding : 9 px var ( -- admin - space - 4 , 10 px ) ; overflow - wrap : anywhere ; border - right : 1 px solid # e4e9f1 ; }
. review - document - header > span : last - child { border - right : 0 ; }
. review - document - row > span : last - child { border - right : 0 ; }
. review - document - row { min - height : 46 px ; border - top : 1 px solid # edf0f4 ; color : # 334155 ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. review - document - row > span { display : flex ; align - items : center ; }
. review - document - type { font - weight : 650 ; }
. review - document - name { min - width : 0 ; gap : var ( -- admin - space - 2 , 6 px ) ; overflow : hidden ; color : # 4 c5b72 ; text - overflow : ellipsis ; }
. review - document - name svg { flex : 0 0 auto ; color : # 8798 cf ; }
. review - document - actions { justify - content : flex - end ; gap : var ( -- admin - space - 2 , 6 px ) ; }
. review - document - actions : deep ( . el - button + . el - button ) { margin - left : 0 ; }
. review - document - state { padding : 14 px ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. review - document - error { padding : 14 px ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. review - document - empty { padding : 14 px ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. review - document - error { display : flex ; align - items : center ; justify - content : space - between ; gap : var ( -- admin - space - 5 , 12 px ) ; margin - bottom : var ( -- admin - space - 3 , 8 px ) ; border : 1 px solid # fecdca ; border - radius : 6 px ; background : # fffafa ; color : # b42318 ; }
. review - document - empty { display : flex ; align - items : center ; justify - content : center ; gap : 7 px ; text - align : center ; } . review - document - empty svg { color : # a5afc1 ; }
. review - decision - card : deep ( . el - form - item ) { margin - bottom : 0 ; } . review - decision - card : deep ( . el - form - item _ _label ) { color : # 4 b5870 ; font - size : var ( -- admin - font - caption , 12 px ) ; font - weight : 650 ; } . decision - summary > div > span { display : block ; margin - top : var ( -- admin - space - 1 , 4 px ) ; } . decision - type { margin : 0 0 14 px ; } . password - input - row { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) auto ; width : 100 % ; gap : var ( -- admin - space - 3 , 8 px ) ; } . dialog - hint { margin : var ( -- admin - space - 5 , 12 px ) 0 0 ; color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; line - height : 1.6 ; } . archive - confirm { display : grid ; gap : var ( -- admin - space - 3 , 8 px ) ; margin : var ( -- admin - space - 6 , 16 px ) 0 ; } . archive - confirm strong { color : var ( -- cc - text ) ; } . archive - confirm span { color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. approval - result - warning { display : flex ; align - items : flex - start ; gap : var ( -- admin - space - 4 , 10 px ) ; padding : var ( -- admin - space - 5 , 12 px ) 14 px ; border : 1 px solid # fecdca ; border - radius : 8 px ; background : # fffafa ; color : # b42318 ; } . approval - result - warning strong { display : block ; } . approval - result - warning span { display : block ; } . approval - result - warning strong { font - size : var ( -- admin - font - section , 14 px ) ; } . approval - result - warning span { margin - top : var ( -- admin - space - 1 , 4 px ) ; font - size : var ( -- admin - font - caption , 12 px ) ; line - height : 1.5 ; } . approval - credentials { margin : 18 px 0 0 ; border : 1 px solid var ( -- cc - border ) ; } . approval - credentials > div { display : grid ; grid - template - columns : 90 px minmax ( 0 , 1 fr ) ; gap : var ( -- admin - space - 5 , 12 px ) ; } . approval - credentials > div + div { border - top : 1 px solid var ( -- cc - border ) ; } . approval - credentials dt { color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; } . approval - credentials dd { display : flex ; min - width : 0 ; align - items : center ; gap : var ( -- admin - space - 4 , 10 px ) ; margin : 0 ; } . approval - credentials code { min - width : 0 ; flex : 1 ; overflow - wrap : anywhere ; color : var ( -- cc - text ) ; font - size : var ( -- admin - font - section , 14 px ) ; font - weight : 700 ; }
@ media ( max - width : 720 px ) { . customer - editor - footer , . review - sync - error { align - items : stretch ; flex - direction : column ; } . customers - table - actions { display : grid ; grid - template - columns : 1 fr 1 fr ; width : 100 % ; } . customers - table - actions : deep ( . el - button ) { min - height : 40 px ; margin : 0 ; } . qualification - table { overflow - x : auto ; } . qualification - row { min - width : 640 px ; } . customer - editor - footer > div { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( 110 px , 1 fr ) ) ; width : 100 % ; } . customer - editor - footer : deep ( . el - button ) { min - height : 40 px ; margin : 0 ; } . decision - summary { align - items : flex - start ; flex - direction : column ; } . review - dialog - grid { grid - template - columns : 1 fr ; gap : 12 px ; } . review - info - grid { grid - template - columns : 1 fr 1 fr ; } . review - document - header , . review - document - row { grid - template - columns : 104 px minmax ( 0 , 1 fr ) 124 px ; } . review - dialog - header { align - items : flex - start ; } . password - input - row { grid - template - columns : 1 fr ; } . table - actions : deep ( . el - button ) { min - height : 40 px ; } }
@ media ( max - width : 480 px ) { : global ( . review - decision - dialog . el - dialog _ _header ) , : global ( . review - decision - dialog . el - dialog _ _body ) { padding - left : 16 px ; padding - right : 16 px ; } . review - info - grid { grid - template - columns : 1 fr ; } . review - document - header { display : none ; } . review - document - row { grid - template - columns : 1 fr ; gap : 6 px ; padding : 10 px ; } . review - document - row > span { padding : 0 ; border - right : 0 ; } . review - document - actions { justify - content : flex - start ; } . review - dialog - title strong { max - width : 190 px ; } }
. decision - summary > div > span { display : block ; margin - top : var ( -- admin - space - 1 , 4 px ) ; } . decision - type { margin : 0 0 14 px ; } . password - input - row { display : grid ; grid - template - columns : minmax ( 0 , 1 fr ) auto ; width : 100 % ; gap : var ( -- admin - space - 3 , 8 px ) ; } . archive - confirm { display : grid ; gap : var ( -- admin - space - 3 , 8 px ) ; margin : var ( -- admin - space - 6 , 16 px ) 0 ; } . archive - confirm strong { color : var ( -- cc - text ) ; } . archive - confirm span { color : var ( -- cc - muted ) ; font - size : var ( -- admin - font - caption , 12 px ) ; }
. approval - result - warning { display : flex ; align - items : flex - start ; gap : var ( -- admin - space - 4 , 10 px ) ; padding : var ( -- admin - space - 5 , 12 px ) 14 px ; border : 1 px solid # fecdca ; border - radius : 8 px ; background : # fffafa ; color : # b42318 ; } . approval - result - warning strong { display : block ; } . approval - result - warning span { display : block ; } . approval - result - warning strong { font - size : var ( -- admin - font - section , 14 px ) ; } . approval - result - warning span { margin - top : var ( -- admin - space - 1 , 4 px ) ; font - size : var ( -- admin - font - caption , 12 px ) ; line - height : 1.5 ; }
@ media ( max - width : 720 px ) { . customer - editor - footer , . review - sync - error { align - items : stretch ; flex - direction : column ; } . customers - table - actions { display : grid ; grid - template - columns : 1 fr 1 fr ; width : 100 % ; } . customers - table - actions : deep ( . el - button ) { min - height : 40 px ; margin : 0 ; } . qualification - table { overflow - x : auto ; } . qualification - row { min - width : 640 px ; } . customer - editor - footer > div { display : grid ; grid - template - columns : repeat ( auto - fit , minmax ( 110 px , 1 fr ) ) ; width : 100 % ; } . customer - editor - footer : deep ( . el - button ) { min - height : 40 px ; margin : 0 ; } . decision - summary { align - items : flex - start ; flex - direction : column ; } . review - document - header , . password - input - row { grid - template - columns : 1 fr ; } . table - actions : deep ( . el - button ) { min - height : 40 px ; } }
@ media ( max - width : 480 px ) { : global ( . review - decision - dialog . el - dialog _ _header ) , : global ( . review - decision - dialog . el - dialog _ _body ) { padding - left : 16 px ; padding - right : 16 px ; } }
@ media ( max - width : 360 px ) { . customers - table - actions , . capital - input { grid - template - columns : 1 fr ; } }
< / style >