feat(订阅式软件提供): 软件管理页新增 API 与密钥、订阅成员弹窗
Group.vue 每个产品新增「API 与密钥」弹窗(激活/查看 ApiKey/重置密钥,密钥仅一次性展示可复制)与「订阅成员」弹窗(选消费者公司 + 部门员工选择器批量添加、列表移除);subscription.ts 增加凭据与成员接口及类型;entityPickerPresets 增加 DeptEmployee 预设;MultiEntityPicker 增加 company 过滤属性。
This commit is contained in:
1 parent
43998fee1d
commit
75cb2b6b97
4 files changed
+452
-1
No files matched your search
@@ -23,6 +23,16 @@ export const entityPickerPresets: Record<string, EntityPickerConfig> = {
|
||||
Workgroup: { api: '/API/FormAPI/WorkgroupSearch', labelField: 'WorkgroupName', valueField: 'WorkgroupUUID', placeholder: '搜索工作组' },
|
||||
// 权限:副文本展示权限码
|
||||
Permission: { api: '/API/FormAPI/PermissionSearch', labelField: 'PermName', valueField: 'PermUUID', secondaryField: 'PermCode', placeholder: '搜索权限' },
|
||||
// 部门员工关联(部门 + 用户-角色对):对齐后端 DEPT_EMPLOYEE_PICKER_MULTI 的取值与分组,
|
||||
// 供「页面直接使用 MultiEntityPicker」的场景(如订阅成员选择);值取 UserRoleDepartmentUUID
|
||||
DeptEmployee: {
|
||||
api: '/API/FormAPI/DeptEmployeeSearch',
|
||||
labelField: 'DeptEmployeeLabel',
|
||||
secondaryField: 'RoleName',
|
||||
valueField: 'UserRoleDepartmentUUID',
|
||||
placeholder: '选择部门员工关联(按部门分组展示)',
|
||||
treeGroup: { groupField: 'DeptUUID', treeField: 'tree', ungroupedTitle: '未分配部门' },
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -59,6 +59,50 @@ export interface SubscriptionAppPartyPayload {
|
||||
Consumers: string[]
|
||||
}
|
||||
|
||||
/** 订阅软件接入凭据状态(不含密钥,密钥只存哈希无法还原) */
|
||||
export interface SubscriptionCredentialState {
|
||||
/** 0=未激活(首次不给 API Key)/ 1=已激活 */
|
||||
Activated: number
|
||||
ApiKey: string
|
||||
CreateTime: string | null
|
||||
ResetTime: string | null
|
||||
}
|
||||
|
||||
/** 激活 / 重置后返回(Secret 明文仅此一次) */
|
||||
export interface SubscriptionCredentialIssued extends SubscriptionCredentialState {
|
||||
Secret: string
|
||||
}
|
||||
|
||||
/** 订阅成员(某产品下的「部门-员工键」订阅记录) */
|
||||
export interface SubscriptionMemberItem {
|
||||
SubscriptionMemberUUID: string
|
||||
SubscriptionGroupUUID: string
|
||||
CompanyUUID: string
|
||||
CompanyName: string
|
||||
DeptUUID: string
|
||||
DeptName: string
|
||||
UserUUID: string
|
||||
UserName: string
|
||||
UserRoleDepartmentUUID: string
|
||||
CreateTime: string | null
|
||||
}
|
||||
|
||||
/** 订阅成员列表响应(含该产品的消费者公司清单,供页面选择) */
|
||||
export interface SubscriptionMemberList {
|
||||
GroupUUID: string
|
||||
GroupName: string
|
||||
Consumers: SubscriptionCompanyItem[]
|
||||
list: SubscriptionMemberItem[]
|
||||
total: number
|
||||
}
|
||||
|
||||
/** 保存订阅成员的返回(逐条结果:成功 Added / 跳过 Skipped) */
|
||||
export interface SubscriptionMemberSaveResult {
|
||||
Added: SubscriptionMemberItem[]
|
||||
Skipped: { Key: string; Reason: string }[]
|
||||
total: number
|
||||
}
|
||||
|
||||
/** 订阅式软件提供 API 客户端 */
|
||||
export const subscriptionApi = {
|
||||
/** 订阅组(产品)列表(含提供者/消费者公司) */
|
||||
@@ -88,4 +132,38 @@ export const subscriptionApi = {
|
||||
/** 保存应用级订阅方(整体重写) */
|
||||
saveAppParties: (data: SubscriptionAppPartyPayload): Promise<SubscriptionAppParties> =>
|
||||
post<SubscriptionAppParties>('/API/Subscription/AppParty/Save', data),
|
||||
|
||||
// ==================== 产品级接入凭据(提供者子服务器鉴权) ====================
|
||||
|
||||
/** 查看某软件的接入凭据状态(不含密钥) */
|
||||
credential: (SubscriptionGroupUUID: string): Promise<SubscriptionCredentialState> =>
|
||||
get<SubscriptionCredentialState>('/API/Subscription/Credential/Get', { SubscriptionGroupUUID }),
|
||||
|
||||
/** 首次激活:生成 API Key + 密钥(密钥明文仅此一次返回) */
|
||||
activateCredential: (SubscriptionGroupUUID: string): Promise<SubscriptionCredentialIssued> =>
|
||||
post<SubscriptionCredentialIssued>('/API/Subscription/Credential/Activate', { SubscriptionGroupUUID }),
|
||||
|
||||
/** 重置密钥(ApiKey 不变,新密钥明文仅此一次返回,旧密钥立即失效) */
|
||||
resetCredentialSecret: (SubscriptionGroupUUID: string): Promise<SubscriptionCredentialIssued> =>
|
||||
post<SubscriptionCredentialIssued>('/API/Subscription/Credential/ResetSecret', { SubscriptionGroupUUID }),
|
||||
|
||||
// ==================== 订阅成员(部门-员工键) ====================
|
||||
|
||||
/** 某软件的订阅成员列表(含该产品的消费者公司清单) */
|
||||
members: (SubscriptionGroupUUID: string): Promise<SubscriptionMemberList> =>
|
||||
get<SubscriptionMemberList>('/API/Subscription/Members', { SubscriptionGroupUUID }),
|
||||
|
||||
/** 新增订阅成员(部门-员工键,批量;部门必须归属该软件的消费者公司) */
|
||||
saveMembers: (
|
||||
SubscriptionGroupUUID: string,
|
||||
UserRoleDepartmentUUIDs: string[],
|
||||
): Promise<SubscriptionMemberSaveResult> =>
|
||||
post<SubscriptionMemberSaveResult>('/API/Subscription/Member/Save', {
|
||||
SubscriptionGroupUUID,
|
||||
UserRoleDepartmentUUIDs,
|
||||
}),
|
||||
|
||||
/** 删除一条订阅成员 */
|
||||
deleteMember: (SubscriptionGroupUUID: string, SubscriptionMemberUUID: string): Promise<void> =>
|
||||
post<void>('/API/Subscription/Member/Delete', { SubscriptionGroupUUID, SubscriptionMemberUUID }),
|
||||
}
|
||||
@@ -45,6 +45,8 @@ const props = withDefaults(defineProps<{
|
||||
placeholder?: string
|
||||
/** 租户过滤:非空时搜索/浏览均按此租户过滤(多选职务按记录所属租户) */
|
||||
tenant?: string
|
||||
/** 公司过滤:非空时搜索/浏览均附带 CompanyUUID(部门员工关联等需显式指定公司的场景) */
|
||||
company?: string
|
||||
/** 回显标签(逗号/顿号分隔,与 modelValue 对齐) */
|
||||
labelText?: string
|
||||
/** 优先展示的分组 key(如部门绑定的通用部门 UUID):该分组置顶展示,其它分组仍可选 */
|
||||
@@ -54,6 +56,7 @@ const props = withDefaults(defineProps<{
|
||||
disabled: false,
|
||||
placeholder: '',
|
||||
tenant: '',
|
||||
company: '',
|
||||
labelText: '',
|
||||
preferredGroupKey: '',
|
||||
})
|
||||
@@ -104,6 +107,8 @@ function rebuildEcho(): void {
|
||||
|
||||
watch(() => props.modelValue, rebuildEcho)
|
||||
watch(() => props.labelText, rebuildEcho)
|
||||
// 公司过滤变化:重建候选选项(旧公司的候选不再适用)
|
||||
watch(() => props.company, () => preload())
|
||||
|
||||
/** 合并选项(已选回显 + 搜索结果,按 value 去重) */
|
||||
function mergeOptions(list: PickerOption[]): void {
|
||||
@@ -116,6 +121,8 @@ function mergeOptions(list: PickerOption[]): void {
|
||||
async function fetchOptions(keyword: string): Promise<{ list: PickerOption[]; hasMore: boolean; tree: TreeGroupNode[] }> {
|
||||
const params: Record<string, unknown> = { keyword }
|
||||
if (props.tenant) params.TenantUUID = props.tenant
|
||||
// 显式公司过滤(空则不发送,后端回退当前角色公司)
|
||||
if (props.company) params.CompanyUUID = props.company
|
||||
// treeGroup 模式:请求附带 tree=1,后端返回通用部门树 + 职务归组
|
||||
if (props.config.treeGroup) params.tree = '1'
|
||||
const res = await get<unknown>(props.config.api, params)
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
* **一家或多家**公司作为消费者。可选公司范围 = 当前管理员的全部可管理公司
|
||||
* (跨租户管理员可选任意租户的公司)。
|
||||
*/
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { setPageTitle } from '@/utils/serverScope'
|
||||
import MultiEntityPicker from '@/components/MultiEntityPicker.vue'
|
||||
import { entityPickerPresets } from '@/api/entityPickerPresets'
|
||||
import {
|
||||
subscriptionApi,
|
||||
type SubscriptionCompanyItem,
|
||||
type SubscriptionCredentialState,
|
||||
type SubscriptionGroupItem,
|
||||
type SubscriptionGroupPayload,
|
||||
type SubscriptionMemberList,
|
||||
} from '@/api/subscription'
|
||||
|
||||
// 同一页面挂两个菜单:软件管理(/subscription/software)与 订阅组管理(/subscription/group)
|
||||
@@ -159,6 +163,180 @@ async function removeGroup(row: SubscriptionGroupItem): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 产品级 API 与密钥 ====================
|
||||
const credVisible = ref(false)
|
||||
const credLoading = ref(false)
|
||||
const credSaving = ref(false)
|
||||
const credGroup = ref<SubscriptionGroupItem | null>(null)
|
||||
const credState = ref<SubscriptionCredentialState | null>(null)
|
||||
/** 一次性密钥明文(激活/重置后返回,刷新或关闭后不再展示) */
|
||||
const credSecret = ref('')
|
||||
|
||||
function formatTime(v: string | null): string {
|
||||
if (!v) return '—'
|
||||
return v.replace('T', ' ').slice(0, 19)
|
||||
}
|
||||
|
||||
async function copyText(text: string): Promise<void> {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
ElMessage.success('已复制')
|
||||
} catch {
|
||||
ElMessage.warning('复制失败,请手动选择文本复制')
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCredential(): Promise<void> {
|
||||
if (!credGroup.value) return
|
||||
credLoading.value = true
|
||||
try {
|
||||
credState.value = await subscriptionApi.credential(credGroup.value.SubscriptionGroupUUID)
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
credLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCredential(row: SubscriptionGroupItem): void {
|
||||
credGroup.value = row
|
||||
credState.value = null
|
||||
credSecret.value = ''
|
||||
credVisible.value = true
|
||||
void loadCredential()
|
||||
}
|
||||
|
||||
async function activateCredential(): Promise<void> {
|
||||
if (!credGroup.value) return
|
||||
credSaving.value = true
|
||||
try {
|
||||
const r = await subscriptionApi.activateCredential(credGroup.value.SubscriptionGroupUUID)
|
||||
credSecret.value = r.Secret
|
||||
ElMessage.success('已激活,请立即保存密钥(仅展示一次)')
|
||||
await loadCredential()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
credSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function resetCredentialSecret(): Promise<void> {
|
||||
if (!credGroup.value) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'重置后旧密钥立即失效,使用旧密钥的子服务器会无法换取访问令牌。确认重置?',
|
||||
'重置密钥',
|
||||
{ type: 'warning', confirmButtonText: '重置', cancelButtonText: '取消' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
credSaving.value = true
|
||||
try {
|
||||
const r = await subscriptionApi.resetCredentialSecret(credGroup.value.SubscriptionGroupUUID)
|
||||
credSecret.value = r.Secret
|
||||
ElMessage.success('已重置,请立即保存新密钥(仅展示一次)')
|
||||
await loadCredential()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
credSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 订阅成员(部门-员工键) ====================
|
||||
const memberVisible = ref(false)
|
||||
const memberLoading = ref(false)
|
||||
const memberSaving = ref(false)
|
||||
const memberGroup = ref<SubscriptionGroupItem | null>(null)
|
||||
const memberData = ref<SubscriptionMemberList | null>(null)
|
||||
/** 当前选择的消费者公司(部门员工选择器的公司范围) */
|
||||
const memberCompanyUUID = ref('')
|
||||
/** 待添加的「部门员工关联」键 */
|
||||
const memberPickKeys = ref<string[]>([])
|
||||
|
||||
const memberConsumers = computed<SubscriptionCompanyItem[]>(() => memberData.value?.Consumers || [])
|
||||
|
||||
// 切换消费者公司后清空已选(旧公司的部门员工键不再适用)
|
||||
watch(memberCompanyUUID, () => {
|
||||
memberPickKeys.value = []
|
||||
})
|
||||
|
||||
async function loadMembers(): Promise<void> {
|
||||
if (!memberGroup.value) return
|
||||
memberLoading.value = true
|
||||
try {
|
||||
memberData.value = await subscriptionApi.members(memberGroup.value.SubscriptionGroupUUID)
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
memberLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openMembers(row: SubscriptionGroupItem): void {
|
||||
memberGroup.value = row
|
||||
memberData.value = null
|
||||
memberCompanyUUID.value = (row.Consumers || [])[0]?.CompanyUUID || ''
|
||||
memberPickKeys.value = []
|
||||
memberVisible.value = true
|
||||
void loadMembers()
|
||||
}
|
||||
|
||||
async function addMembers(): Promise<void> {
|
||||
if (!memberGroup.value) return
|
||||
if (!memberCompanyUUID.value) {
|
||||
ElMessage.warning('请先选择消费者公司')
|
||||
return
|
||||
}
|
||||
if (!memberPickKeys.value.length) {
|
||||
ElMessage.warning('请先选择「部门员工关联」')
|
||||
return
|
||||
}
|
||||
memberSaving.value = true
|
||||
try {
|
||||
const r = await subscriptionApi.saveMembers(
|
||||
memberGroup.value.SubscriptionGroupUUID,
|
||||
memberPickKeys.value,
|
||||
)
|
||||
const added = r.Added?.length || 0
|
||||
const skipped = r.Skipped || []
|
||||
if (added) ElMessage.success(`已新增 ${added} 位订阅成员`)
|
||||
if (skipped.length) ElMessage.warning(skipped.map((s) => s.Reason).join(';'))
|
||||
if (!added && !skipped.length) ElMessage.info('没有可新增的订阅成员')
|
||||
memberPickKeys.value = []
|
||||
await loadMembers()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
} finally {
|
||||
memberSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeMember(row: Record<string, unknown>): Promise<void> {
|
||||
if (!memberGroup.value) return
|
||||
const memberUUID = String(row.SubscriptionMemberUUID || '')
|
||||
const label = `${row.DeptName || ''} · ${row.UserName || ''}`
|
||||
if (!memberUUID) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认移除「${label}」的订阅?`,
|
||||
'移除确认',
|
||||
{ type: 'warning', confirmButtonText: '移除', cancelButtonText: '取消' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await subscriptionApi.deleteMember(memberGroup.value.SubscriptionGroupUUID, memberUUID)
|
||||
ElMessage.success('已移除')
|
||||
await loadMembers()
|
||||
} catch {
|
||||
/* 拦截器已提示 */
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([load(), loadCompanies()])
|
||||
})
|
||||
@@ -192,6 +370,12 @@ onMounted(async () => {
|
||||
{{ g.GroupStatus ? '启用' : '停用' }}
|
||||
</el-tag>
|
||||
<div class="spacer" />
|
||||
<el-button link type="primary" @click="openCredential(g)">
|
||||
<el-icon><Key /></el-icon>API 与密钥
|
||||
</el-button>
|
||||
<el-button link type="primary" @click="openMembers(g)">
|
||||
<el-icon><UserFilled /></el-icon>订阅成员
|
||||
</el-button>
|
||||
<el-button link type="primary" @click="openEdit(g)">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-button>
|
||||
@@ -306,6 +490,129 @@ onMounted(async () => {
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 产品级 API 与密钥:提供者的子服务器用这对凭据换取访问令牌 -->
|
||||
<el-dialog
|
||||
v-model="credVisible"
|
||||
:title="`API 与密钥 — ${credGroup?.GroupName || ''}`"
|
||||
width="640px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-loading="credLoading" class="cred-body">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="提供者的子服务器用这对凭据调用主服务器换取访问令牌,从而启用本软件(产品)的功能。"
|
||||
/>
|
||||
<template v-if="credState">
|
||||
<!-- 未激活 -->
|
||||
<div v-if="!credState.Activated" class="cred-empty">
|
||||
<el-empty description="尚未激活 API 与密钥(首次不下发,需手动激活)" :image-size="80" />
|
||||
<el-button type="primary" :loading="credSaving" @click="activateCredential">
|
||||
<el-icon><Key /></el-icon>激活 API 与密钥
|
||||
</el-button>
|
||||
</div>
|
||||
<!-- 已激活 -->
|
||||
<template v-else>
|
||||
<el-form label-width="96px" class="cred-form">
|
||||
<el-form-item label="API Key">
|
||||
<el-input :model-value="credState.ApiKey" readonly>
|
||||
<template #append>
|
||||
<el-button @click="copyText(credState.ApiKey)">复制</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="激活时间">{{ formatTime(credState.CreateTime) }}</el-form-item>
|
||||
<el-form-item label="最近重置">{{ formatTime(credState.ResetTime) }}</el-form-item>
|
||||
</el-form>
|
||||
<el-alert v-if="credSecret" type="warning" :closable="false" show-icon class="cred-secret">
|
||||
<template #title>密钥(仅展示一次,请立即保存)</template>
|
||||
<div class="secret-row">
|
||||
<el-input :model-value="credSecret" readonly />
|
||||
<el-button type="primary" @click="copyText(credSecret)">复制</el-button>
|
||||
</div>
|
||||
</el-alert>
|
||||
<div class="cred-foot">
|
||||
<el-button type="danger" plain :loading="credSaving" @click="resetCredentialSecret">
|
||||
重置密钥
|
||||
</el-button>
|
||||
<span class="field-tip">重置后旧密钥立即失效,ApiKey 不变</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="credVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 订阅成员:以「部门-员工键」为单位,部门必须归属该产品的消费者公司 -->
|
||||
<el-dialog
|
||||
v-model="memberVisible"
|
||||
:title="`订阅成员 — ${memberGroup?.GroupName || ''}`"
|
||||
width="840px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-loading="memberLoading" class="member-body">
|
||||
<el-alert
|
||||
v-if="!memberConsumers.length"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="该产品还没有消费者公司,请先在「编辑」里指定消费者公司后再添加订阅成员。"
|
||||
/>
|
||||
<div class="member-add">
|
||||
<el-select
|
||||
v-model="memberCompanyUUID"
|
||||
placeholder="选择消费者公司"
|
||||
class="member-company"
|
||||
:disabled="!memberConsumers.length"
|
||||
>
|
||||
<el-option
|
||||
v-for="c in memberConsumers"
|
||||
:key="c.CompanyUUID"
|
||||
:label="c.CompanyName"
|
||||
:value="c.CompanyUUID"
|
||||
/>
|
||||
</el-select>
|
||||
<MultiEntityPicker
|
||||
v-model="memberPickKeys"
|
||||
:config="entityPickerPresets.DeptEmployee"
|
||||
:company="memberCompanyUUID"
|
||||
:disabled="!memberCompanyUUID"
|
||||
placeholder="选择部门员工关联(按部门分组展示)"
|
||||
class="member-picker"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="memberSaving"
|
||||
:disabled="!memberConsumers.length"
|
||||
@click="addMembers"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>添加
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="field-tip">订阅以「部门-员工键」为单位;所选部门必须归属该产品的消费者公司。</div>
|
||||
<el-table :data="memberData?.list || []" size="small" class="member-table">
|
||||
<el-table-column prop="CompanyName" label="消费者公司" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="DeptName" label="部门" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="UserName" label="员工" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="danger" @click="removeMember(row)">移除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #empty>
|
||||
<el-empty description="暂无订阅成员" :image-size="60" />
|
||||
</template>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="memberVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -387,4 +694,53 @@ onMounted(async () => {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
/* API 与密钥弹窗 */
|
||||
.cred-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.cred-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 0 4px;
|
||||
}
|
||||
.cred-form {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.cred-secret .secret-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.cred-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
/* 订阅成员弹窗 */
|
||||
.member-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.member-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.member-company {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.member-picker {
|
||||
flex: 1;
|
||||
min-width: 260px;
|
||||
}
|
||||
.member-table {
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user