fix: optimize sso integration layout
This commit is contained in:
@@ -299,38 +299,63 @@ function showError(error: unknown, fallback: string) {
|
||||
<el-button :loading="savingUrl" @click="savePublicUrl">保存地址</el-button>
|
||||
</section>
|
||||
|
||||
<el-tabs :model-value="activeTab" @tab-change="switchTab">
|
||||
<el-tabs class="sso-tabs" :model-value="activeTab" @tab-change="switchTab">
|
||||
<el-tab-pane label="接入应用" name="applications">
|
||||
<el-table :data="clients" stripe empty-text="还没有接入应用">
|
||||
<el-table-column prop="name" label="应用名称" min-width="180" />
|
||||
<el-table-column prop="appId" label="应用ID" min-width="180" />
|
||||
<el-table-column label="回跳地址" min-width="240" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.redirectUris.join("、") || "未配置" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="identityCount" label="绑定用户" width="100" align="center" />
|
||||
<el-table-column label="新用户策略" min-width="190">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.allowAutoRegister">自动注册 · {{ row.defaultEntitlementPlanName || "未配置权益" }}</span>
|
||||
<span v-else>仅已有学员</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最近使用" width="180">
|
||||
<template #default="{ row }">{{ formatTime(row.lastUsedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.status" :active-value="1" :inactive-value="0" @change="toggleClient(row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="190" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<div class="sso-row-actions">
|
||||
<el-button size="small" @click="openEditDialog(row)">编辑</el-button>
|
||||
<el-button size="small" @click="rotateSecret(row)">更新密钥</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="sso-table-shell">
|
||||
<el-table :data="clients" stripe empty-text="还没有接入应用" table-layout="fixed" style="width: 100%">
|
||||
<el-table-column label="应用" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<div class="sso-app-cell">
|
||||
<strong>{{ row.name }}</strong>
|
||||
<span>{{ row.appId }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="账号策略" min-width="170">
|
||||
<template #default="{ row }">
|
||||
<div class="sso-policy-cell">
|
||||
<span v-if="row.allowAutoRegister">自动注册 · {{ row.defaultEntitlementPlanName || "未配置权益" }}</span>
|
||||
<span v-else>仅已有学员</span>
|
||||
<small>已绑定 {{ row.identityCount }} 人</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="回跳地址" min-width="170" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.redirectUris.join("、") || "未配置" }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="运行状态" width="148">
|
||||
<template #default="{ row }">
|
||||
<div class="sso-status-cell">
|
||||
<div><el-switch v-model="row.status" :active-value="1" :inactive-value="0" @change="toggleClient(row)" /><span>{{ row.status === 1 ? "已启用" : "已停用" }}</span></div>
|
||||
<small>最近使用:{{ formatTime(row.lastUsedAt) }}</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="right">
|
||||
<template #default="{ row }">
|
||||
<div class="sso-row-actions">
|
||||
<el-button size="small" @click="openEditDialog(row)">编辑</el-button>
|
||||
<el-button size="small" @click="rotateSecret(row)">更新密钥</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="sso-app-cards">
|
||||
<el-empty v-if="!clients.length" description="还没有接入应用" :image-size="72" />
|
||||
<article v-for="client in clients" v-else :key="client.id" class="sso-app-card">
|
||||
<div class="sso-app-card-head">
|
||||
<div class="sso-app-cell"><strong>{{ client.name }}</strong><span>{{ client.appId }}</span></div>
|
||||
<div class="sso-card-status"><el-switch v-model="client.status" :active-value="1" :inactive-value="0" @change="toggleClient(client)" /><span>{{ client.status === 1 ? "已启用" : "已停用" }}</span></div>
|
||||
</div>
|
||||
<dl>
|
||||
<div><dt>账号策略</dt><dd>{{ client.allowAutoRegister ? `自动注册 · ${client.defaultEntitlementPlanName || "未配置权益"}` : "仅已有学员" }}(已绑定 {{ client.identityCount }} 人)</dd></div>
|
||||
<div><dt>回跳地址</dt><dd>{{ client.redirectUris.join("、") || "未配置" }}</dd></div>
|
||||
<div><dt>最近使用</dt><dd>{{ formatTime(client.lastUsedAt) }}</dd></div>
|
||||
</dl>
|
||||
<div class="sso-card-actions"><el-button size="small" @click="openEditDialog(client)">编辑</el-button><el-button size="small" @click="rotateSecret(client)">更新密钥</el-button></div>
|
||||
</article>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="账号绑定" name="identities">
|
||||
@@ -386,28 +411,50 @@ function showError(error: unknown, fallback: string) {
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-dialog v-model="clientDialogOpen" :title="dialogTitle" width="620px" destroy-on-close>
|
||||
<el-form label-position="top">
|
||||
<div class="sso-form-grid">
|
||||
<el-form-item label="应用名称"><el-input v-model="clientForm.name" placeholder="例如:学员服务 App" /></el-form-item>
|
||||
<el-form-item label="应用ID"><el-input v-model="clientForm.appId" :disabled="Boolean(editingClientId)" placeholder="例如:student-app" /></el-form-item>
|
||||
</div>
|
||||
<el-form-item label="允许回跳地址">
|
||||
<el-input v-model="clientForm.redirectUrisText" type="textarea" :rows="4" placeholder="每行一个完整 HTTPS 地址;不需要回跳时可以留空" />
|
||||
</el-form-item>
|
||||
<div class="sso-auto-register">
|
||||
<div>
|
||||
<strong>自动注册新学员</strong>
|
||||
<p>可信应用传入本地尚不存在的已验证手机号和姓名时,自动创建学员并分配默认权益。</p>
|
||||
<el-dialog v-model="clientDialogOpen" class="sso-client-dialog" :title="dialogTitle" width="min(720px, calc(100vw - 32px))" destroy-on-close align-center>
|
||||
<el-form class="sso-client-form" label-position="top">
|
||||
<section class="sso-form-section">
|
||||
<div class="sso-section-heading">
|
||||
<strong>基本信息</strong>
|
||||
<span>用于识别接入方,应用 ID 创建后不可修改。</span>
|
||||
</div>
|
||||
<el-switch v-model="clientForm.allowAutoRegister" />
|
||||
</div>
|
||||
<el-form-item v-if="clientForm.allowAutoRegister" label="默认权益版本">
|
||||
<el-select v-model="clientForm.defaultEntitlementPlanId" placeholder="请选择自动注册学员的权益" style="width: 100%">
|
||||
<el-option v-for="plan in entitlementPlans" :key="plan.id" :label="plan.name" :value="plan.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用应用"><el-switch v-model="clientForm.status" :active-value="1" :inactive-value="0" /></el-form-item>
|
||||
<div class="sso-form-grid">
|
||||
<el-form-item label="应用名称"><el-input v-model="clientForm.name" placeholder="例如:学员服务 App" /></el-form-item>
|
||||
<el-form-item label="应用 ID"><el-input v-model="clientForm.appId" :disabled="Boolean(editingClientId)" placeholder="例如:student-app" /></el-form-item>
|
||||
</div>
|
||||
</section>
|
||||
<section class="sso-form-section">
|
||||
<div class="sso-section-heading">
|
||||
<strong>登录后回跳</strong>
|
||||
<span>每行一个完整 HTTPS 地址,不需要回跳时可以留空。</span>
|
||||
</div>
|
||||
<el-form-item class="sso-redirect-field" label="允许回跳地址">
|
||||
<el-input v-model="clientForm.redirectUrisText" type="textarea" :rows="3" placeholder="https://student.example.com/home" />
|
||||
</el-form-item>
|
||||
</section>
|
||||
<section class="sso-form-section">
|
||||
<div class="sso-section-heading">
|
||||
<strong>访问与账号策略</strong>
|
||||
<span>控制应用是否可用,以及首次进入时如何处理新学员。</span>
|
||||
</div>
|
||||
<div class="sso-setting-list">
|
||||
<div class="sso-setting-row">
|
||||
<div><strong>启用应用</strong><span>停用后,该应用将无法继续申请或使用免登录。</span></div>
|
||||
<el-switch v-model="clientForm.status" :active-value="1" :inactive-value="0" />
|
||||
</div>
|
||||
<div class="sso-setting-row">
|
||||
<div><strong>自动注册新学员</strong><span>传入已验证手机号和姓名时,可自动创建账号并分配权益。</span></div>
|
||||
<el-switch v-model="clientForm.allowAutoRegister" />
|
||||
</div>
|
||||
<div v-if="clientForm.allowAutoRegister" class="sso-entitlement-setting">
|
||||
<el-form-item label="新学员默认权益">
|
||||
<el-select v-model="clientForm.defaultEntitlementPlanId" placeholder="请选择权益版本" style="width: 100%">
|
||||
<el-option v-for="plan in entitlementPlans" :key="plan.id" :label="plan.name" :value="plan.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="clientDialogOpen = false">取消</el-button>
|
||||
@@ -430,21 +477,382 @@ function showError(error: unknown, fallback: string) {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sso-page { display: grid; gap: 20px; }
|
||||
.sso-public-url { display: grid; grid-template-columns: minmax(250px, 1fr) minmax(320px, 1.3fr) auto; gap: 18px; align-items: center; padding: 20px 22px; border: 1px solid #dce8e4; border-radius: 16px; background: #fff; }
|
||||
.sso-public-url p { margin: 6px 0 0; color: #71817d; font-size: 13px; }
|
||||
.sso-row-actions { display: flex; justify-content: center; gap: 8px; white-space: nowrap; }
|
||||
.sso-filters { display: flex; gap: 12px; margin-bottom: 16px; }
|
||||
.sso-filters .el-select { width: 190px; }
|
||||
.sso-filters .el-input { max-width: 360px; }
|
||||
.sso-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
||||
.sso-auto-register { display: flex; align-items: center; justify-content: space-between; gap: 24px; margin-bottom: 18px; padding: 16px; border: 1px solid #dce8e4; border-radius: 12px; background: #f8fbfa; }
|
||||
.sso-auto-register p { margin: 6px 0 0; color: #71817d; font-size: 13px; line-height: 1.6; }
|
||||
.sso-secret-block { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 12px 16px; align-items: start; margin-top: 20px; }
|
||||
.sso-secret-block label { color: #6c7d79; }
|
||||
.sso-secret-block code { padding: 10px 12px; overflow-wrap: anywhere; border-radius: 8px; background: #f3f7f5; color: #173d34; }
|
||||
@media (max-width: 980px) {
|
||||
.sso-public-url { grid-template-columns: 1fr; }
|
||||
.sso-filters { flex-wrap: wrap; }
|
||||
.sso-page {
|
||||
container-type: inline-size;
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sso-public-url {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(230px, 1fr) minmax(280px, 1.2fr) auto;
|
||||
gap: 18px;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
padding: 20px 22px;
|
||||
border: 1px solid #dce8e4;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sso-public-url > *,
|
||||
.sso-tabs,
|
||||
.sso-table-shell {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sso-public-url p {
|
||||
margin: 6px 0 0;
|
||||
color: #71817d;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:deep(.sso-tabs .el-tabs__content),
|
||||
:deep(.sso-tabs .el-tab-pane) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.sso-table-shell {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
border: 1px solid #e1eae7;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sso-app-cell,
|
||||
.sso-policy-cell,
|
||||
.sso-status-cell {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sso-app-cell strong,
|
||||
.sso-policy-cell span {
|
||||
overflow: hidden;
|
||||
color: #253b35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sso-app-cell span,
|
||||
.sso-policy-cell small,
|
||||
.sso-status-cell small {
|
||||
overflow: hidden;
|
||||
color: #80908b;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sso-status-cell > div,
|
||||
.sso-card-status {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
color: #4f625c;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sso-row-actions,
|
||||
.sso-card-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sso-row-actions :deep(.el-button + .el-button),
|
||||
.sso-card-actions :deep(.el-button + .el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sso-app-cards {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sso-filters {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sso-filters .el-select {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
.sso-filters .el-input {
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.sso-client-form {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sso-form-section {
|
||||
min-width: 0;
|
||||
padding: 18px 20px;
|
||||
border: 1px solid #e0e9e6;
|
||||
border-radius: 14px;
|
||||
background: #fbfcfc;
|
||||
}
|
||||
|
||||
.sso-section-heading {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sso-section-heading strong {
|
||||
color: #203a33;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.sso-section-heading span {
|
||||
color: #7a8b86;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.sso-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sso-client-form :deep(.el-form-item) {
|
||||
min-width: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.sso-client-form :deep(.el-form-item__label) {
|
||||
height: auto;
|
||||
padding-bottom: 7px;
|
||||
color: #465a54;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.sso-client-form :deep(.el-input__wrapper) {
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
.sso-redirect-field :deep(.el-textarea__inner) {
|
||||
min-height: 82px !important;
|
||||
padding: 11px 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.sso-setting-list {
|
||||
overflow: hidden;
|
||||
border: 1px solid #e0e9e6;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sso-setting-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 24px;
|
||||
align-items: center;
|
||||
min-height: 72px;
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.sso-setting-row + .sso-setting-row,
|
||||
.sso-entitlement-setting {
|
||||
border-top: 1px solid #e7eeec;
|
||||
}
|
||||
|
||||
.sso-setting-row > div {
|
||||
display: grid;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sso-setting-row strong {
|
||||
color: #324942;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sso-setting-row span {
|
||||
color: #7a8b86;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.sso-entitlement-setting {
|
||||
padding: 14px 16px 16px;
|
||||
background: #f5faf8;
|
||||
}
|
||||
|
||||
:deep(.sso-client-dialog) {
|
||||
max-height: calc(100vh - 32px);
|
||||
overflow: hidden;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
:deep(.sso-client-dialog .el-dialog__header) {
|
||||
margin-right: 0;
|
||||
padding: 22px 26px 17px;
|
||||
border-bottom: 1px solid #e7eeec;
|
||||
}
|
||||
|
||||
:deep(.sso-client-dialog .el-dialog__title) {
|
||||
color: #1f3932;
|
||||
font-size: 21px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:deep(.sso-client-dialog .el-dialog__body) {
|
||||
max-height: calc(100vh - 178px);
|
||||
overflow-y: auto;
|
||||
padding: 20px 26px;
|
||||
}
|
||||
|
||||
:deep(.sso-client-dialog .el-dialog__footer) {
|
||||
padding: 16px 26px 20px;
|
||||
border-top: 1px solid #e7eeec;
|
||||
}
|
||||
|
||||
.sso-secret-block {
|
||||
display: grid;
|
||||
grid-template-columns: 90px minmax(0, 1fr);
|
||||
gap: 12px 16px;
|
||||
align-items: start;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sso-secret-block label {
|
||||
color: #6c7d79;
|
||||
}
|
||||
|
||||
.sso-secret-block code {
|
||||
padding: 10px 12px;
|
||||
overflow-wrap: anywhere;
|
||||
border-radius: 8px;
|
||||
background: #f3f7f5;
|
||||
color: #173d34;
|
||||
}
|
||||
|
||||
@container (max-width: 940px) {
|
||||
.sso-public-url {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.sso-public-url > div {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.sso-table-shell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sso-app-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.sso-app-cards > .el-empty {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.sso-app-card {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
padding: 18px;
|
||||
border: 1px solid #dfe9e6;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.sso-app-card-head {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.sso-card-status {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.sso-app-card dl {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sso-app-card dl > div {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sso-app-card dt {
|
||||
color: #82918d;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sso-app-card dd {
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: #41554f;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
}
|
||||
|
||||
@container (max-width: 660px) {
|
||||
.sso-app-cards,
|
||||
.sso-public-url {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.sso-public-url > div {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.sso-public-url .el-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sso-filters {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sso-filters .el-select,
|
||||
.sso-filters .el-input {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.sso-form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sso-form-section {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:deep(.sso-client-dialog .el-dialog__header),
|
||||
:deep(.sso-client-dialog .el-dialog__body),
|
||||
:deep(.sso-client-dialog .el-dialog__footer) {
|
||||
padding-right: 18px;
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user