feat(document-permissions): 重构权限页面按模板接入真实接口
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
import { getDepartments } from '@/api/management/organization'
|
||||
@@ -11,87 +11,160 @@ import {
|
||||
isPermissionMockEnabled,
|
||||
} from '@/api/management/document-permissions'
|
||||
import { getRoles } from '@/api/management/roles'
|
||||
import { getUsers } from '@/api/management/users'
|
||||
import type { DocumentRecord, TemplatePermissionRecord } from '@/api/management/types'
|
||||
import type {
|
||||
DepartmentRecord,
|
||||
DocumentRecord,
|
||||
RoleRecord,
|
||||
TemplatePermissionRecord,
|
||||
} from '@/api/management/types'
|
||||
|
||||
import DepartmentPermissionTable from './components/DepartmentPermissionTable.vue'
|
||||
import RolePermissionMatrix from './components/RolePermissionMatrix.vue'
|
||||
import TemplatePermissionPanel from './components/TemplatePermissionPanel.vue'
|
||||
import { departmentPermissionRows, permissionColumns, rolePermissionRows } from './mock'
|
||||
import type {
|
||||
DepartmentPermissionRow,
|
||||
PermissionCellState,
|
||||
PermissionKey,
|
||||
PermissionSubjectOption,
|
||||
RolePermissionRow,
|
||||
TemplatePermissionForm,
|
||||
} from './types'
|
||||
|
||||
type PermissionSubjectType = 'ROLE' | 'DEPARTMENT'
|
||||
|
||||
interface PermissionResolution {
|
||||
enabled: boolean
|
||||
state: PermissionCellState
|
||||
}
|
||||
|
||||
const permissionKeys: PermissionKey[] = ['VIEW', 'USE', 'MAINTAIN']
|
||||
|
||||
const loading = ref(true)
|
||||
const roles = ref<RolePermissionRow[]>([])
|
||||
const departments = ref<DepartmentPermissionRow[]>([])
|
||||
const roleRecords = ref<RoleRecord[]>([])
|
||||
const departmentRecords = ref<DepartmentRecord[]>([])
|
||||
const roleRows = ref<RolePermissionRow[]>([])
|
||||
const departmentRows = ref<DepartmentPermissionRow[]>([])
|
||||
const templates = ref<DocumentRecord[]>([])
|
||||
const selectedTemplateId = ref('')
|
||||
const templatePermissions = ref<TemplatePermissionRecord[]>([])
|
||||
const permissionSubjects = ref<PermissionSubjectOption[]>([])
|
||||
const permissionLoading = ref(false)
|
||||
const permissionSaving = ref(false)
|
||||
const permissionError = ref(false)
|
||||
let permissionRequestId = 0
|
||||
|
||||
const selectedTemplate = computed(() =>
|
||||
templates.value.find((template) => template.id === selectedTemplateId.value),
|
||||
)
|
||||
|
||||
function resolvePermission(
|
||||
subjectType: PermissionSubjectType,
|
||||
subjectId: string,
|
||||
permission: PermissionKey,
|
||||
): PermissionResolution {
|
||||
const records = templatePermissions.value.filter(
|
||||
(record) =>
|
||||
record.subjectType === subjectType &&
|
||||
record.subjectId === subjectId &&
|
||||
record.permissionLevel === permission,
|
||||
)
|
||||
const hasDirectDeny = records.some((record) => !record.inherited && record.effect === 'DENY')
|
||||
const hasDirectAllow = records.some((record) => !record.inherited && record.effect === 'ALLOW')
|
||||
const hasInheritedDeny = records.some((record) => record.inherited && record.effect === 'DENY')
|
||||
const hasInheritedAllow = records.some((record) => record.inherited && record.effect === 'ALLOW')
|
||||
|
||||
if (hasDirectDeny) {
|
||||
return { enabled: false, state: 'direct-deny' }
|
||||
}
|
||||
|
||||
if (hasInheritedDeny) {
|
||||
return { enabled: false, state: 'inherited-deny' }
|
||||
}
|
||||
|
||||
if (hasDirectAllow) {
|
||||
return { enabled: true, state: 'direct-allow' }
|
||||
}
|
||||
|
||||
if (hasInheritedAllow) {
|
||||
return { enabled: true, state: 'inherited-allow' }
|
||||
}
|
||||
|
||||
return { enabled: false, state: 'none' }
|
||||
}
|
||||
|
||||
function buildPermissionMaps(subjectType: PermissionSubjectType, subjectId: string) {
|
||||
const permissions = {} as Record<PermissionKey, boolean>
|
||||
const permissionStates = {} as Record<PermissionKey, PermissionCellState>
|
||||
|
||||
for (const permission of permissionKeys) {
|
||||
const resolution = resolvePermission(subjectType, subjectId, permission)
|
||||
permissions[permission] = resolution.enabled
|
||||
permissionStates[permission] = resolution.state
|
||||
}
|
||||
|
||||
return { permissions, permissionStates }
|
||||
}
|
||||
|
||||
function syncPermissionRows() {
|
||||
roleRows.value = roleRecords.value.map((role, index) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
color: role.color ?? ['#6b5bd2', '#0e6e8c', '#0f9d6c', '#d9821f'][index % 4],
|
||||
description: role.description,
|
||||
userCount: role.userCount ?? 0,
|
||||
...buildPermissionMaps('ROLE', role.id),
|
||||
}))
|
||||
|
||||
departmentRows.value = departmentRecords.value.map((department) => ({
|
||||
id: department.id,
|
||||
department: department.name,
|
||||
...buildPermissionMaps('DEPARTMENT', department.id),
|
||||
}))
|
||||
}
|
||||
|
||||
async function loadPermissions() {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
if (isPermissionMockEnabled) {
|
||||
roles.value = rolePermissionRows.map((role) => ({
|
||||
roleRows.value = rolePermissionRows.map((role) => ({
|
||||
...role,
|
||||
permissions: { ...role.permissions },
|
||||
}))
|
||||
departments.value = departmentPermissionRows.map((row) => ({ ...row }))
|
||||
departmentRows.value = departmentPermissionRows.map((row) => ({
|
||||
...row,
|
||||
permissions: { ...row.permissions },
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
const [templatesResult, rolesResult, departmentsResult, usersResult] = await Promise.allSettled(
|
||||
[
|
||||
getDocuments({ page: 1, pageSize: 200, status: 'all' }),
|
||||
getRoles(),
|
||||
getDepartments(),
|
||||
getUsers({ page: 1, pageSize: 200, status: 'all' }),
|
||||
],
|
||||
)
|
||||
const [templatesResult, rolesResult, departmentsResult] = await Promise.allSettled([
|
||||
getDocuments({ page: 1, pageSize: 200, status: 'all' }),
|
||||
getRoles({ page: 1, pageSize: 200 }),
|
||||
getDepartments(),
|
||||
])
|
||||
|
||||
if (templatesResult.status === 'rejected') {
|
||||
throw templatesResult.reason
|
||||
}
|
||||
|
||||
templates.value = templatesResult.value.records
|
||||
roleRecords.value = rolesResult.status === 'fulfilled' ? rolesResult.value : []
|
||||
departmentRecords.value =
|
||||
departmentsResult.status === 'fulfilled' ? departmentsResult.value : []
|
||||
syncPermissionRows()
|
||||
|
||||
if (rolesResult.status === 'rejected' || departmentsResult.status === 'rejected') {
|
||||
ElMessage.warning('角色或科室列表加载不完整,部分授权对象暂不可配置')
|
||||
}
|
||||
|
||||
selectedTemplateId.value = templates.value[0]?.id ?? ''
|
||||
permissionSubjects.value = [
|
||||
...(rolesResult.status === 'fulfilled'
|
||||
? rolesResult.value.map((role) => ({
|
||||
type: 'ROLE' as const,
|
||||
id: role.id,
|
||||
label: role.name,
|
||||
}))
|
||||
: []),
|
||||
...(departmentsResult.status === 'fulfilled'
|
||||
? departmentsResult.value.map((department) => ({
|
||||
type: 'DEPARTMENT' as const,
|
||||
id: department.id,
|
||||
label: department.name,
|
||||
}))
|
||||
: []),
|
||||
...(usersResult.status === 'fulfilled'
|
||||
? usersResult.value.records.map((user) => ({
|
||||
type: 'USER' as const,
|
||||
id: user.id,
|
||||
label: `${user.name}${user.employeeNo ? `(${user.employeeNo})` : ''}`,
|
||||
}))
|
||||
: []),
|
||||
]
|
||||
await loadTemplatePermissions()
|
||||
} catch {
|
||||
roleRecords.value = []
|
||||
departmentRecords.value = []
|
||||
roleRows.value = []
|
||||
departmentRows.value = []
|
||||
templates.value = []
|
||||
permissionSubjects.value = []
|
||||
templatePermissions.value = []
|
||||
permissionError.value = false
|
||||
ElMessage.error('权限配置加载失败,请稍后重试')
|
||||
} finally {
|
||||
loading.value = false
|
||||
@@ -99,76 +172,170 @@ async function loadPermissions() {
|
||||
}
|
||||
|
||||
async function loadTemplatePermissions() {
|
||||
if (!selectedTemplateId.value || isPermissionMockEnabled) {
|
||||
const templateId = selectedTemplateId.value
|
||||
const requestId = ++permissionRequestId
|
||||
|
||||
if (!templateId || isPermissionMockEnabled) {
|
||||
templatePermissions.value = []
|
||||
permissionError.value = false
|
||||
return
|
||||
}
|
||||
|
||||
permissionLoading.value = true
|
||||
permissionError.value = false
|
||||
|
||||
try {
|
||||
templatePermissions.value = await getTemplatePermissions(selectedTemplateId.value)
|
||||
const records = await getTemplatePermissions(templateId)
|
||||
|
||||
if (requestId !== permissionRequestId || templateId !== selectedTemplateId.value) {
|
||||
return
|
||||
}
|
||||
|
||||
templatePermissions.value = records
|
||||
syncPermissionRows()
|
||||
} catch {
|
||||
if (requestId !== permissionRequestId) {
|
||||
return
|
||||
}
|
||||
|
||||
templatePermissions.value = []
|
||||
permissionError.value = true
|
||||
syncPermissionRows()
|
||||
ElMessage.error('模板权限加载失败,请稍后重试')
|
||||
} finally {
|
||||
permissionLoading.value = false
|
||||
if (requestId === permissionRequestId) {
|
||||
permissionLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleRolePermission(roleId: string, permission: PermissionKey) {
|
||||
const role = roles.value.find((item) => item.id === roleId)
|
||||
|
||||
if (!role) {
|
||||
if (isPermissionMockEnabled) {
|
||||
const role = roleRows.value.find((item) => item.id === roleId)
|
||||
if (role) {
|
||||
role.permissions[permission] = !role.permissions[permission]
|
||||
ElMessage.info('演示数据已调整,真实环境将通过权限接口保存')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
role.permissions[permission] = !role.permissions[permission]
|
||||
ElMessage.info('权限已调整并记录审计日志')
|
||||
void setSubjectPermission('ROLE', roleId, permission)
|
||||
}
|
||||
|
||||
function adjustDepartmentPermission(row: DepartmentPermissionRow) {
|
||||
ElMessage.info(`原型演示:调整${row.department}文档权限`)
|
||||
}
|
||||
|
||||
async function addTemplatePermission(form: TemplatePermissionForm) {
|
||||
if (!selectedTemplateId.value) {
|
||||
function toggleDepartmentPermission(departmentId: string, permission: PermissionKey) {
|
||||
if (isPermissionMockEnabled) {
|
||||
const row = departmentRows.value.find((item) => item.id === departmentId)
|
||||
if (row) {
|
||||
row.permissions[permission] = !row.permissions[permission]
|
||||
ElMessage.info('演示数据已调整,真实环境将通过权限接口保存')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
void setSubjectPermission('DEPARTMENT', departmentId, permission)
|
||||
}
|
||||
|
||||
async function applyTemplatePermission(
|
||||
templateId: string,
|
||||
subjectType: PermissionSubjectType,
|
||||
subjectId: string,
|
||||
permission: PermissionKey,
|
||||
enabled: boolean,
|
||||
) {
|
||||
const matchingRecords = templatePermissions.value.filter(
|
||||
(record) =>
|
||||
record.subjectType === subjectType &&
|
||||
record.subjectId === subjectId &&
|
||||
record.permissionLevel === permission,
|
||||
)
|
||||
const directAllows = matchingRecords.filter(
|
||||
(record) => !record.inherited && record.effect === 'ALLOW',
|
||||
)
|
||||
const directDenies = matchingRecords.filter(
|
||||
(record) => !record.inherited && record.effect === 'DENY',
|
||||
)
|
||||
const inheritedAllows = matchingRecords.some(
|
||||
(record) => record.inherited && record.effect === 'ALLOW',
|
||||
)
|
||||
|
||||
if (enabled) {
|
||||
for (const binding of directDenies) {
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接拒绝权限缺少绑定 ID,无法撤销')
|
||||
}
|
||||
await deleteTemplatePermission(templateId, binding.id)
|
||||
}
|
||||
|
||||
if (!directAllows.length && !inheritedAllows) {
|
||||
await createTemplatePermission(templateId, {
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'ALLOW',
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for (const binding of directAllows) {
|
||||
if (!binding.id) {
|
||||
throw new Error('该直接允许权限缺少绑定 ID,无法撤销')
|
||||
}
|
||||
await deleteTemplatePermission(templateId, binding.id)
|
||||
}
|
||||
|
||||
if (!directDenies.length && inheritedAllows) {
|
||||
await createTemplatePermission(templateId, {
|
||||
subjectType,
|
||||
subjectId,
|
||||
permissionLevel: permission,
|
||||
effect: 'DENY',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function setSubjectPermission(
|
||||
subjectType: PermissionSubjectType,
|
||||
subjectId: string,
|
||||
permission: PermissionKey,
|
||||
) {
|
||||
if (!selectedTemplateId.value || permissionSaving.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const current = resolvePermission(subjectType, subjectId, permission)
|
||||
|
||||
if (current.state === 'inherited-deny') {
|
||||
ElMessage.warning('该权限由上级规则拒绝,当前接口不支持直接覆盖')
|
||||
return
|
||||
}
|
||||
|
||||
const nextEnabled = !current.enabled
|
||||
permissionSaving.value = true
|
||||
|
||||
try {
|
||||
await createTemplatePermission(selectedTemplateId.value, form)
|
||||
await applyTemplatePermission(
|
||||
selectedTemplateId.value,
|
||||
subjectType,
|
||||
subjectId,
|
||||
permission,
|
||||
nextEnabled,
|
||||
)
|
||||
await loadTemplatePermissions()
|
||||
ElMessage.success('模板权限已添加')
|
||||
} catch {
|
||||
ElMessage.error('模板权限添加失败,请稍后重试')
|
||||
ElMessage.success(nextEnabled ? '权限已开启' : '权限已关闭')
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '权限调整失败,请稍后重试')
|
||||
} finally {
|
||||
permissionSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTemplatePermissionRecord(permissionId: string) {
|
||||
if (!selectedTemplateId.value || !window.confirm('确定删除这条模板权限吗?')) {
|
||||
return
|
||||
}
|
||||
|
||||
permissionSaving.value = true
|
||||
|
||||
try {
|
||||
await deleteTemplatePermission(selectedTemplateId.value, permissionId)
|
||||
await loadTemplatePermissions()
|
||||
ElMessage.success('模板权限已删除')
|
||||
} catch {
|
||||
ElMessage.error('模板权限删除失败,请稍后重试')
|
||||
} finally {
|
||||
permissionSaving.value = false
|
||||
}
|
||||
function handleTemplateChange(event: Event) {
|
||||
selectedTemplateId.value = (event.target as HTMLSelectElement).value
|
||||
void loadTemplatePermissions()
|
||||
}
|
||||
|
||||
function selectTemplate(templateId: string) {
|
||||
selectedTemplateId.value = templateId
|
||||
function retryTemplatePermissions() {
|
||||
void loadTemplatePermissions()
|
||||
}
|
||||
|
||||
@@ -196,44 +363,79 @@ onMounted(loadPermissions)
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isPermissionMockEnabled">
|
||||
<template v-else>
|
||||
<section class="page-card">
|
||||
<div class="card-heading">
|
||||
<h2>按角色授权</h2>
|
||||
<div class="card-heading section-heading">
|
||||
<div class="heading-copy">
|
||||
<h2>按角色授权</h2>
|
||||
<p>选择文档模板后,调整各个角色的可见、可用和可维护权限。</p>
|
||||
</div>
|
||||
<label v-if="!isPermissionMockEnabled" class="template-select">
|
||||
<span>当前文档模板</span>
|
||||
<select :value="selectedTemplateId" @change="handleTemplateChange">
|
||||
<option value="" disabled>请选择模板</option>
|
||||
<option v-for="template in templates" :key="template.id" :value="template.id">
|
||||
{{ template.name }} · {{ template.version }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="!isPermissionMockEnabled && !selectedTemplateId" class="empty-state">
|
||||
暂无可配置的文档模板
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionLoading" class="loading-state">
|
||||
正在加载模板权限…
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionError" class="error-state">
|
||||
<span>模板权限加载失败</span>
|
||||
<button type="button" class="retry-button" @click="retryTemplatePermissions">
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
<RolePermissionMatrix
|
||||
:rows="roles"
|
||||
v-else
|
||||
:rows="roleRows"
|
||||
:columns="permissionColumns"
|
||||
:saving="permissionSaving"
|
||||
@toggle="toggleRolePermission"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="page-card department-card">
|
||||
<div class="card-heading">
|
||||
<h2>按科室授权</h2>
|
||||
<span>科室级别的文档可用范围</span>
|
||||
<div class="card-heading section-heading">
|
||||
<div class="heading-copy">
|
||||
<h2>按科室授权</h2>
|
||||
<p>
|
||||
{{ selectedTemplate ? selectedTemplate.name : '当前文档模板' }}:调整各个科室的权限。
|
||||
</p>
|
||||
</div>
|
||||
<span v-if="selectedTemplate" class="template-meta">
|
||||
{{ selectedTemplate.code || selectedTemplate.id }} · {{ selectedTemplate.category }}
|
||||
</span>
|
||||
</div>
|
||||
<DepartmentPermissionTable :rows="departments" @adjust="adjustDepartmentPermission" />
|
||||
|
||||
<div v-if="!isPermissionMockEnabled && !selectedTemplateId" class="empty-state">
|
||||
暂无可配置的文档模板
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionLoading" class="loading-state">
|
||||
正在加载模板权限…
|
||||
</div>
|
||||
<div v-else-if="!isPermissionMockEnabled && permissionError" class="error-state">
|
||||
<span>模板权限加载失败</span>
|
||||
<button type="button" class="retry-button" @click="retryTemplatePermissions">
|
||||
重新加载
|
||||
</button>
|
||||
</div>
|
||||
<DepartmentPermissionTable
|
||||
v-else
|
||||
:rows="departmentRows"
|
||||
:columns="permissionColumns"
|
||||
:saving="permissionSaving"
|
||||
@toggle="toggleDepartmentPermission"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<section v-else class="page-card">
|
||||
<div class="card-heading">
|
||||
<h2>模板权限</h2>
|
||||
<span>按模板配置角色、科室和用户的可见/可用/可维护权限</span>
|
||||
</div>
|
||||
<TemplatePermissionPanel
|
||||
:templates="templates"
|
||||
:selected-template-id="selectedTemplateId"
|
||||
:permissions="templatePermissions"
|
||||
:subjects="permissionSubjects"
|
||||
:loading="permissionLoading"
|
||||
:saving="permissionSaving"
|
||||
@update:selected-template-id="selectTemplate"
|
||||
@add="addTemplatePermission"
|
||||
@delete="deleteTemplatePermissionRecord"
|
||||
/>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -263,6 +465,15 @@ onMounted(loadPermissions)
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.heading-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-heading h2 {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
@@ -270,8 +481,14 @@ onMounted(loadPermissions)
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.card-heading span {
|
||||
margin-left: auto;
|
||||
.heading-copy p {
|
||||
margin: 5px 0 0;
|
||||
color: var(--mut);
|
||||
font-size: 11.5px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.card-heading > span {
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -283,8 +500,37 @@ onMounted(loadPermissions)
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
gap: 6px;
|
||||
min-width: min(360px, 100%);
|
||||
color: var(--mut);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 0;
|
||||
padding: 7px 10px;
|
||||
color: var(--ink);
|
||||
font: inherit;
|
||||
background: #fff;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
padding-bottom: 8px;
|
||||
font-size: 11.5px !important;
|
||||
}
|
||||
|
||||
.department-card {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -317,6 +563,38 @@ onMounted(loadPermissions)
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.empty-state,
|
||||
.error-state {
|
||||
padding: 28px 16px;
|
||||
color: var(--mut);
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
background: #f8fbfc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--err);
|
||||
}
|
||||
|
||||
.retry-button {
|
||||
padding: 4px 10px;
|
||||
color: var(--brand);
|
||||
font-size: 12px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--brand);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.retry-button:hover {
|
||||
background: var(--brand-l);
|
||||
}
|
||||
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
background-position: 100% 0;
|
||||
@@ -327,13 +605,28 @@ onMounted(loadPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.section-heading {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.template-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.card-heading {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-heading span {
|
||||
.card-heading > span {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user