角色管理和审批模板管理完成
This commit is contained in:
@@ -0,0 +1,547 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, h, onMounted } from 'vue';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { NButton, NPopconfirm, NTag } from 'naive-ui';
|
||||
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const btnLoading = ref<boolean>(false);
|
||||
const currentType = ref<string>('add');
|
||||
const readonly = computed(() => currentType.value === "view");
|
||||
const formRef = ref<any>(null);
|
||||
const currentTemplate = ref<{ id: number, category: string | null, level: string | null, timeLimit: string | null, steps: any[] }>({
|
||||
id: 0,
|
||||
category: null,
|
||||
level: null,
|
||||
timeLimit: null,
|
||||
steps: [],
|
||||
});
|
||||
const rules = ref({
|
||||
category: { required: true, message: '变更类别不能为空', trigger: ['blur'] },
|
||||
level: { required: true, message: '变更等级不能为空', trigger: ['blur'] },
|
||||
timeLimit: { required: true, message: '变更时效不能为空', trigger: ['blur'] },
|
||||
step: { required: true, message: '审批步骤不能为空', trigger: ['blur'] }
|
||||
})
|
||||
const templateModal = ref<boolean>(false);
|
||||
|
||||
const categoryOptions = ref([
|
||||
{
|
||||
label: '工艺',
|
||||
value: '工艺'
|
||||
},
|
||||
{
|
||||
label: '设备',
|
||||
value: '设备'
|
||||
},
|
||||
{
|
||||
label: '管理',
|
||||
value: '管理'
|
||||
}
|
||||
])
|
||||
|
||||
const levelOptions = ref([
|
||||
{
|
||||
label: '一般',
|
||||
value: '一般'
|
||||
},
|
||||
{
|
||||
label: '重要',
|
||||
value: '重要'
|
||||
},
|
||||
])
|
||||
|
||||
const timeLimitOptions = ref([
|
||||
{
|
||||
label: '永久',
|
||||
value: '永久'
|
||||
},
|
||||
{
|
||||
label: '临时',
|
||||
value: '临时'
|
||||
},
|
||||
])
|
||||
const SPECIALTIES = ref<{ label: string, value: string }[]>([
|
||||
{ label: '工艺', value: 'process' },
|
||||
{ label: '设备', value: 'equipment' },
|
||||
{ label: '仪表', value: 'instrument' },
|
||||
{ label: '电气', value: 'electrical' },
|
||||
{ label: '安全', value: 'safety' },
|
||||
{ label: '质量', value: 'quality' }
|
||||
]);
|
||||
|
||||
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
pageCount: 0,
|
||||
})
|
||||
const columns = ref([
|
||||
{
|
||||
title: '序号',
|
||||
key: 'index',
|
||||
width: 6,
|
||||
align: 'center',
|
||||
render: (row: any, index: number) => {
|
||||
return h('span', {}, { default: () => (pagination.value.page - 1) * pagination.value.pageSize + index + 1 })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '变更类别',
|
||||
key: 'category',
|
||||
width: 8,
|
||||
align: 'center',
|
||||
render: (row: any, index: number) => {
|
||||
return h(NTag, {
|
||||
size: 'small',
|
||||
type: 'default'
|
||||
}, { default: () => `${row.category}` })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '变更等级',
|
||||
key: 'level',
|
||||
width: 8,
|
||||
align: 'center',
|
||||
render: (row: any, index: number) => {
|
||||
return h(NTag, {
|
||||
size: 'small',
|
||||
type: `${row.level==='重要'?'error':'default'}`
|
||||
}, { default: () => `${row.level}` })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '变更时效',
|
||||
key: 'timeLimit',
|
||||
width: 8,
|
||||
align: 'center',
|
||||
render: (row: any, index: number) => {
|
||||
return h(NTag, {
|
||||
size: 'small',
|
||||
type: `${row.timeLimit==='临时'?'warning':'info'}`
|
||||
}, { default: () => `${row.timeLimit}` })
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '审批步骤',
|
||||
key: 'step',
|
||||
width: 44,
|
||||
align: 'center',
|
||||
render: (row: any) => {
|
||||
const stepNames = row.steps?.map((step: any) => step.name) || [];
|
||||
const text = stepNames.join(' → ');
|
||||
return h('p', { class: 'text-left text-xs' }, text || '');
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 12,
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
render: (row: any) => {
|
||||
return h('div', { class: 'flex items-center justify-center gap-3' }, {
|
||||
default: () => [
|
||||
h(NButton, {
|
||||
text: true,
|
||||
size: 'tiny',
|
||||
type: 'primary',
|
||||
onClick: () => handleView(row)
|
||||
}, {
|
||||
// 渲染按钮文本
|
||||
default: () => '查看'
|
||||
}),
|
||||
h(NButton, {
|
||||
text: true,
|
||||
size: 'tiny',
|
||||
type: 'primary',
|
||||
onClick: () => handleEdit(row)
|
||||
}, {
|
||||
// 渲染按钮文本
|
||||
default: () => '编辑'
|
||||
}),
|
||||
h(NPopconfirm, {
|
||||
// 组件 Props
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
handleDelete(row)
|
||||
},
|
||||
// 可选:自定义确认/取消按钮的样式
|
||||
positiveButtonProps: { size: 'tiny', type: 'primary' },
|
||||
negativeButtonProps: { size: 'tiny' }
|
||||
}, {
|
||||
// 插槽 (Slots)
|
||||
trigger: () => h(NButton,
|
||||
{
|
||||
text: true,
|
||||
size: 'tiny',
|
||||
type: 'error',
|
||||
},
|
||||
{
|
||||
default: () => '删除' // 按钮文字
|
||||
}
|
||||
),
|
||||
default: () => '确定要删除吗?' // 弹出框的提示内容
|
||||
}),
|
||||
]
|
||||
})
|
||||
}
|
||||
},
|
||||
])
|
||||
const list = ref<{ id: number, category: string, level: string, timeLimit: string, steps: any[] }[]>([])
|
||||
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
list.value = [
|
||||
{id:1,category:'工艺',level:'重要',timeLimit:'临时',steps: [
|
||||
{
|
||||
id:101,
|
||||
name:'部门审核',
|
||||
countersign: false,
|
||||
members: [{person:'测试人员01',specialty:'',countersign:false}]
|
||||
},
|
||||
{
|
||||
id:102,
|
||||
name:'专业审批',
|
||||
countersign: false,
|
||||
members: [{person:'测试人员02',specialty:'',countersign:false}]
|
||||
},
|
||||
{id: 103,
|
||||
name: "部门负责人批准",
|
||||
countersign: false,
|
||||
members: [ {person:'测试人员03',specialty:'',countersign:false}]
|
||||
}
|
||||
]},
|
||||
{id:2,category:'设备',level:'一般',timeLimit:'临时',steps:[
|
||||
|
||||
]},
|
||||
{id:3,category:'管理',level:'重要',timeLimit:'永久',steps:[
|
||||
|
||||
]},
|
||||
{id:4,category:'工艺',level:'重要',timeLimit:'临时',steps:[
|
||||
|
||||
]},
|
||||
|
||||
]
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 打开添加模板弹框
|
||||
const addTemplate = () => {
|
||||
currentType.value = 'add';
|
||||
currentTemplate.value = {
|
||||
id: 0,
|
||||
category: null,
|
||||
level: null,
|
||||
timeLimit: null,
|
||||
steps: [],
|
||||
};
|
||||
templateModal.value = true;
|
||||
}
|
||||
// 查看模板
|
||||
const handleView = (row: any) => {
|
||||
currentType.value = 'view';
|
||||
currentTemplate.value = {
|
||||
id: row.id,
|
||||
category: row.category,
|
||||
level: row.level,
|
||||
timeLimit: row.timeLimit,
|
||||
steps: row.steps,
|
||||
};
|
||||
templateModal.value = true;
|
||||
}
|
||||
// 编辑角色
|
||||
const handleEdit = (row: any) => {
|
||||
currentType.value = 'edit';
|
||||
currentTemplate.value = {
|
||||
id: row.id,
|
||||
category: row.category,
|
||||
level: row.level,
|
||||
timeLimit: row.timeLimit,
|
||||
steps: row.steps,
|
||||
};
|
||||
templateModal.value = true;
|
||||
}
|
||||
// 删除角色
|
||||
const handleDelete = (row: any) => {
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
list.value = list.value.filter((item: any) => item.id !== row.id);
|
||||
window.$message?.success('删除成功')
|
||||
}, 1000)
|
||||
}
|
||||
// 保存角色
|
||||
const saveRole = async (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
formRef.value?.validate((errors: any) => {
|
||||
if (!errors) {
|
||||
btnLoading.value = true;
|
||||
setTimeout(() => {
|
||||
// 新增
|
||||
if(currentType.value === 'add'){
|
||||
list.value.push({
|
||||
id:list.value.length+1,
|
||||
category:currentTemplate.value?.category as string,
|
||||
level:currentTemplate.value?.level as string,
|
||||
timeLimit:currentTemplate.value?.timeLimit as string,
|
||||
steps:currentTemplate.value?.steps as any[]
|
||||
})
|
||||
}
|
||||
// 编辑
|
||||
if(currentType.value === 'edit'){
|
||||
const target = list.value.find((item:any) => item.id === currentTemplate.value.id);
|
||||
if (target) {
|
||||
target.category = currentTemplate.value?.category as string;
|
||||
target.level = currentTemplate.value?.level as string;
|
||||
target.timeLimit = currentTemplate.value?.timeLimit as string;
|
||||
target.steps = currentTemplate.value?.steps as any[];
|
||||
}
|
||||
}
|
||||
window.$message?.success('操作成功')
|
||||
btnLoading.value = false;
|
||||
templateModal.value = false;
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 会签步骤:一个模板最多一步;只有会签步骤能选专业。
|
||||
// 勾选/取消后,所有非会签步骤的人员专业与「必须会签」标记一并清空
|
||||
const setStepCountersign = (target: any, v: boolean) => {
|
||||
currentTemplate.value.steps.forEach((s:any) => {
|
||||
s.countersign = s === target ? v : false;
|
||||
if (!s.countersign) s.members.forEach((m:any) => { m.specialty = ""; m.countersign = false; });
|
||||
});
|
||||
}
|
||||
// 当前打开人员选择器的步骤(步骤行「+」按钮切换)
|
||||
const pickerStepId = ref<number | null>(null);
|
||||
const pick = ref<string | null>(null);
|
||||
const togglePicker = (s: any) => {
|
||||
pickerStepId.value = pickerStepId.value === s.id ? null : s.id;
|
||||
pick.value = null;
|
||||
}
|
||||
const addMember = (s: any, v: string | null) => {
|
||||
if (v && !s.members.some((m:any) => m.person === v)) s.members.push({ person: v, specialty: "", countersign: false });
|
||||
pick.value = null;
|
||||
pickerStepId.value = null;
|
||||
}
|
||||
const delMember = (s: any, p: string) => {
|
||||
s.members = s.members.filter((m:any) => m.person !== p);
|
||||
}
|
||||
const removeStep = (idx: number) => {
|
||||
currentTemplate.value.steps.splice(idx, 1);
|
||||
}
|
||||
let sid = 100;
|
||||
const mkStep = (name: string, extra: Partial<any> = {}): any => ({ id: ++sid, name, countersign: false, members: [], ...extra });
|
||||
const addStepAfter = (idx: number) => {
|
||||
currentTemplate.value.steps.splice(idx + 1, 0, mkStep(""));
|
||||
}
|
||||
const ORG_TREE = [
|
||||
{
|
||||
id: "factory", name: "演示工厂", short: "YS", leaf: false, sort: 0,
|
||||
children: [
|
||||
{
|
||||
id: "scb", name: "生产部", short: "SCB", leaf: false, sort: 0,
|
||||
children: [
|
||||
{ id: "cj1", name: "测试车间一", short: "CJ1", leaf: true, sort: 0 },
|
||||
{ id: "cj2", name: "测试车间二", short: "CJ2", leaf: true, sort: 1 },
|
||||
{ id: "cj3", name: "测试车间三", short: "CJ3", leaf: true, sort: 2 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "ahb", name: "安环部", short: "AHB", leaf: false, sort: 1,
|
||||
children: [
|
||||
{ id: "aqk", name: "安全科", short: "AQK", leaf: true, sort: 0 },
|
||||
{ id: "hbk", name: "环保科", short: "HBK", leaf: true, sort: 1 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sbb", name: "设备部", short: "SBB", leaf: false, sort: 2,
|
||||
children: [
|
||||
{ id: "sbk", name: "设备科", short: "SBK", leaf: true, sort: 0 },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "zlb", name: "质量部", short: "ZLB", leaf: false, sort: 3,
|
||||
children: [
|
||||
{ id: "zjk", name: "质检科", short: "ZJK", leaf: true, sort: 0 },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const LEVEL2_ORGS = ORG_TREE[0]?.children ?? [];
|
||||
const ORG_MEMBERS = [
|
||||
{ id: 1, name: "测试人员01", dept: "测试车间一", gender: "男", phone: "13800000001", email: "test01@demo.com", account: "TEST001", role: "操作人员", scope: ["factory"], enabled: true },
|
||||
{ id: 2, name: "测试人员02", dept: "测试车间二", gender: "女", phone: "13800000002", email: "test02@demo.com", account: "TEST002", role: "班长", scope: ["factory"], enabled: true },
|
||||
{ id: 3, name: "测试人员03", dept: "测试车间三", gender: "男", phone: "13800000003", email: "test03@demo.com", account: "TEST003", role: "车间主任", scope: ["factory"], enabled: true },
|
||||
{ id: 4, name: "测试人员04", dept: "安全科", gender: "女", phone: "13800000004", email: "test04@demo.com", account: "TEST004", role: "安全工程师", scope: ["factory"], enabled: true },
|
||||
{ id: 5, name: "测试人员05", dept: "环保科", gender: "男", phone: "13800000005", email: "test05@demo.com", account: "TEST005", role: "环保专员", scope: ["factory"], enabled: true },
|
||||
{ id: 6, name: "测试人员06", dept: "设备科", gender: "男", phone: "13800000006", email: "test06@demo.com", account: "TEST006", role: "设备工程师", scope: ["factory"], enabled: true },
|
||||
{ id: 7, name: "测试人员07", dept: "质检科", gender: "女", phone: "13800000007", email: "test07@demo.com", account: "TEST007", role: "质量工程师", scope: ["factory"], enabled: true },
|
||||
];
|
||||
function orgSubtreeNames(n: any): string[] {
|
||||
return [n.name, ...(n.children ?? []).flatMap(orgSubtreeNames)];
|
||||
}
|
||||
// 预设人员:级联选项(第二层组织 → 组织下可选成员,已选成员不再出现,空组织不展示)
|
||||
const peopleOpts = (s: any) =>
|
||||
LEVEL2_ORGS.map((o) => ({
|
||||
label: o.name,
|
||||
value: o.id,
|
||||
children: ORG_MEMBERS
|
||||
.filter((m) => m.enabled && orgSubtreeNames(o).includes(m.dept) && !s.members.some((x) => x.person === m.name))
|
||||
.map((m) => ({ label: m.name, value: m.name })),
|
||||
})).filter((o) => o.children.length > 0);
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<!-- 角色管理 -->
|
||||
<NCard :bordered="false" size="small" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<div class="border mb-3 py-2 px-3" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||
<NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
|
||||
<NGi span="24 s:12 m:12">
|
||||
<div class="h-100% flex items-center gap-2">
|
||||
<Icon icon="carbon:flow-connection" class="size-18px" :style="{ color: themeStore.themeColor }" />
|
||||
<p class="text-[16px] font-600">审批模板管理</p>
|
||||
</div>
|
||||
</NGi>
|
||||
<NGi span="24 s:12 m:12" class="flex items-center justify-end gap-12px">
|
||||
<NButton type="primary" size="small" @click="addTemplate">
|
||||
<Icon icon="ic:round-plus" class="size-16px" />
|
||||
添加模板
|
||||
</NButton>
|
||||
</NGi>
|
||||
</NGrid>
|
||||
</div>
|
||||
<div class="scroll">
|
||||
<NDataTable
|
||||
size="small"
|
||||
:loading="loading"
|
||||
:single-line="false"
|
||||
:columns="columns"
|
||||
:data="list"
|
||||
:pagination="pagination"
|
||||
max-height="calc(100vh - 290px)"
|
||||
:scroll-x="820"
|
||||
/>
|
||||
</div>
|
||||
<!-- 新增/编辑/查看弹框 -->
|
||||
<NModal
|
||||
v-model:show="templateModal"
|
||||
preset="card"
|
||||
:title="currentType === 'add' ? '新增模板' : currentType === 'edit' ? '编辑模板':'查看模板'"
|
||||
:auto-focus="false"
|
||||
:style="{ width: '800px', height: 'auto' }"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
>
|
||||
<template #default>
|
||||
<NForm class="w-full" ref="formRef" :model="currentTemplate" :rules="rules" label-placement="left" label-width="auto">
|
||||
<NGrid :cols="24" :x-gap="24">
|
||||
<NFormItemGi :span="8" label="变更类别" path="category">
|
||||
<NSelect
|
||||
v-model:value="currentTemplate.category"
|
||||
:options="categoryOptions"
|
||||
:disabled="readonly"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :span="8" label="变更等级" path="level">
|
||||
<NSelect
|
||||
v-model:value="currentTemplate.level"
|
||||
:options="levelOptions"
|
||||
:disabled="readonly"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi :span="8" label="变更时效" path="timeLimit">
|
||||
<NSelect
|
||||
v-model:value="currentTemplate.timeLimit"
|
||||
:options="timeLimitOptions"
|
||||
:disabled="readonly"
|
||||
placeholder="请选择"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
<!-- 审批步骤 -->
|
||||
<div class="flex flex-col gap-3 mb-3">
|
||||
<div v-for="(s, idx) in currentTemplate.steps" :key="s.id" class="rounded-lg border border-slate-100 bg-slate-50/40 p-3">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="w-7 shrink-0 text-center text-xs text-slate-400">{{ idx + 1 }}</span>
|
||||
<NInput v-model:value="s.name" maxlength="12" :disabled="readonly" placeholder="步骤名称(如:部门审核)" class="!w-48 text-sm" />
|
||||
<NCheckbox :checked="s.countersign" :disabled="readonly" size="small" @update:checked="(v) => setStepCountersign(s, v)">
|
||||
<span class="text-xs text-slate-500">专业会签步骤</span>
|
||||
</NCheckbox>
|
||||
<NButton v-if="!readonly" ghost type="primary" size="tiny" @click="togglePicker(s)" title="添加预设人员" class="ml-auto">
|
||||
<Icon icon="ic:round-plus" class="size-16px" />
|
||||
</NButton>
|
||||
<NButton v-if="!readonly" text type="error" @click="removeStep(idx)" title="删除该步骤">
|
||||
<Icon icon="iconamoon:trash" class="size-16px" />
|
||||
</NButton>
|
||||
</div>
|
||||
<!-- 预设人员:人员必选;仅会签步骤可选择专业(必选),且人员可勾选「必须进行专业会签」 -->
|
||||
<div class="mt-2 flex gap-2 pl-10">
|
||||
<span class="shrink-0 text-xs leading-7 text-slate-400">预设人员:</span>
|
||||
<div class="min-w-0 flex-1 space-y-1.5">
|
||||
<div v-for="m in s.members" :key="m.person" class="flex min-h-7 flex-wrap items-center gap-2">
|
||||
<span class="w-20 shrink-0 truncate text-xs font-medium text-slate-700">{{ m.person }}</span>
|
||||
<template v-if="s.countersign">
|
||||
<span class="text-xs text-slate-400"><span class="mr-0.5 text-red-500">*</span>专业:</span>
|
||||
<NSelect :value="m.specialty || null" clearable size="small" placeholder="请选择专业" class="w-28"
|
||||
:options="SPECIALTIES"
|
||||
@update:value="(v) => m.specialty = v ?? ''"
|
||||
/>
|
||||
<NCheckbox :checked="m.countersign" size="small"
|
||||
@update:checked="(v) => m.countersign = v"
|
||||
>
|
||||
<span class="text-xs text-slate-500">必须参与会签</span>
|
||||
</NCheckbox>
|
||||
</template>
|
||||
<NButton v-if="!readonly" text type="error" @click="delMember(s, m.person)" title="删除该人员">
|
||||
<Icon icon="iconamoon:trash" class="size-16px" />
|
||||
</NButton>
|
||||
</div>
|
||||
<p v-if="s.countersign && s.members.length" class="text-[11px] leading-5 text-slate-400">
|
||||
必须参与会签:勾选的人员必须参与专业会签,未勾选的人员为可选会签(按需参加)
|
||||
</p>
|
||||
<NCascader v-if="!readonly && pickerStepId === s.id" :value="pick" :options="peopleOpts(s)"
|
||||
placeholder="请选择人员" size="small" check-strategy="child" :show-path="false" expand-trigger="click"
|
||||
class="w-56" @update:value="(v) => addMember(s, v == null ? null : String(v))" />
|
||||
<span v-if="readonly && s.members.length === 0" class="text-xs leading-7 text-slate-300">未预设</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NButton v-if="!readonly" size="small" ghost dashed class="text-xs" @click="addStepAfter(currentTemplate.steps.length - 1)"><Icon icon="ic:round-plus" class="size-14px" />添加审批步骤</NButton>
|
||||
</NForm>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div v-if="!readonly" class="flex justify-end gap-[10px]">
|
||||
<NButton @click="templateModal = false">取消</NButton>
|
||||
<NButton type="primary" :loading="btnLoading" @click="saveRole">确定</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
</NCard>
|
||||
</NSpace>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll {
|
||||
height: calc(100vh - 205px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user