导入 AKSRT 主题开发源码
This commit is contained in:
1 parent
9681dd3d48
commit
1268a55dd0
595 files changed
+127705
No files matched your search
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="基本权限 / 能力矩阵"
|
||||
desc="统一管理用户治理、投稿和评论的角色能力矩阵。"
|
||||
>
|
||||
<template #actions>
|
||||
<n-space>
|
||||
<n-button quaternary @click="resetDefaults">恢复默认</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</PageHeader>
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 240px;">
|
||||
<n-card class="aksrt-section-card" v-if="summary.cap_count">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>矩阵概览</h3>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-tag round type="info">能力项:{{ summary.cap_count }}</n-tag>
|
||||
<n-tag round type="info">分组:{{ summary.group_count }}</n-tag>
|
||||
<n-tag round type="info">角色列:{{ summary.column_count }}</n-tag>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card v-for="group in groups" :key="group.key" class="aksrt-section-card">
|
||||
<template #header>
|
||||
<div class="aksrt-cap-matrix__header">
|
||||
<div>
|
||||
<h3 class="aksrt-cap-matrix__title">{{ group.title }}</h3>
|
||||
<p class="aksrt-cap-matrix__desc">共 {{ group.items.length }} 项能力</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<n-data-table
|
||||
:columns="buildColumns(group)"
|
||||
:data="group.items"
|
||||
:bordered="true"
|
||||
size="small"
|
||||
:single-line="false"
|
||||
:scroll-x="1200"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<div class="aksrt-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存能力矩阵</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue'
|
||||
import { NInputNumber, NSwitch, NTag } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const groups = ref([])
|
||||
const columnsMeta = ref({})
|
||||
const summary = reactive({
|
||||
cap_count: 0,
|
||||
group_count: 0,
|
||||
column_count: 0,
|
||||
})
|
||||
const userCaps = reactive({})
|
||||
const defaultsSnapshot = ref({})
|
||||
|
||||
onMounted(load)
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.getCapabilities()
|
||||
const data = res?.data || {}
|
||||
|
||||
columnsMeta.value = data.columns || {}
|
||||
groups.value = data.groups || []
|
||||
Object.assign(summary, data.summary || { cap_count: 0, group_count: 0, column_count: 0 })
|
||||
|
||||
const snapshot = {}
|
||||
for (const group of groups.value) {
|
||||
for (const item of group.items || []) {
|
||||
snapshot[item.cap_id] = { ...(item.values || {}) }
|
||||
}
|
||||
}
|
||||
|
||||
replaceReactive(userCaps, snapshot)
|
||||
defaultsSnapshot.value = JSON.parse(JSON.stringify(snapshot))
|
||||
} catch (error) {
|
||||
message.error(error?.message || '加载能力矩阵失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function replaceReactive(target, source) {
|
||||
Object.keys(target).forEach((key) => delete target[key])
|
||||
Object.entries(source).forEach(([key, value]) => {
|
||||
target[key] = { ...value }
|
||||
})
|
||||
}
|
||||
|
||||
function buildColumns(group) {
|
||||
const cols = [
|
||||
{
|
||||
title: '能力项',
|
||||
key: 'label',
|
||||
fixed: 'left',
|
||||
width: 280,
|
||||
render: (row) => h('div', { class: 'aksrt-cap-matrix__cap' }, [
|
||||
h('strong', row.label),
|
||||
h('code', row.cap_id),
|
||||
]),
|
||||
},
|
||||
]
|
||||
|
||||
const visibleColumns = new Set()
|
||||
for (const item of group.items || []) {
|
||||
for (const col of item.visible_columns || []) {
|
||||
visibleColumns.add(col)
|
||||
}
|
||||
}
|
||||
|
||||
for (const col of Object.keys(columnsMeta.value || {})) {
|
||||
if (!visibleColumns.has(col)) continue
|
||||
|
||||
cols.push({
|
||||
title: columnsMeta.value[col],
|
||||
key: col,
|
||||
width: col === 'level' || col === 'vip' ? 110 : 100,
|
||||
render: (row) => renderCapCell(row, col),
|
||||
})
|
||||
}
|
||||
|
||||
return cols
|
||||
}
|
||||
|
||||
function renderCapCell(row, col) {
|
||||
const hidden = !(row.visible_columns || []).includes(col)
|
||||
if (hidden) {
|
||||
return h('span', { class: 'aksrt-cap-matrix__empty' }, '—')
|
||||
}
|
||||
|
||||
if (!userCaps[row.cap_id]) {
|
||||
userCaps[row.cap_id] = { ...(row.values || {}) }
|
||||
}
|
||||
|
||||
if (col === 'level' || col === 'vip') {
|
||||
return h(NInputNumber, {
|
||||
value: userCaps[row.cap_id]?.[col] ?? -1,
|
||||
min: -1,
|
||||
max: col === 'level' ? 10 : 2,
|
||||
step: 1,
|
||||
size: 'small',
|
||||
style: 'width: 90px',
|
||||
onUpdateValue: (value) => updateCapValue(row.cap_id, col, value ?? -1),
|
||||
})
|
||||
}
|
||||
|
||||
return h(NSwitch, {
|
||||
value: !!userCaps[row.cap_id]?.[col],
|
||||
size: 'small',
|
||||
onUpdateValue: (value) => updateCapValue(row.cap_id, col, value),
|
||||
})
|
||||
}
|
||||
|
||||
function updateCapValue(capId, col, value) {
|
||||
if (!userCaps[capId]) {
|
||||
userCaps[capId] = {}
|
||||
}
|
||||
userCaps[capId][col] = value
|
||||
}
|
||||
|
||||
function resetDefaults() {
|
||||
replaceReactive(userCaps, JSON.parse(JSON.stringify(defaultsSnapshot.value || {})))
|
||||
message.info('已恢复默认矩阵')
|
||||
}
|
||||
|
||||
function expandAll() {
|
||||
message.info('当前矩阵为平铺展示,无需展开')
|
||||
}
|
||||
|
||||
function collapseAll() {
|
||||
message.info('当前矩阵为平铺展示,无需折叠')
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.saveCapabilities(JSON.parse(JSON.stringify(userCaps)))
|
||||
defaultsSnapshot.value = JSON.parse(JSON.stringify(userCaps))
|
||||
message.success('能力矩阵已保存')
|
||||
} catch (error) {
|
||||
message.error(error?.message || '保存能力矩阵失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.aksrt-cap-matrix__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.aksrt-cap-matrix__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.aksrt-cap-matrix__desc {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
color: #8a909c;
|
||||
}
|
||||
|
||||
.aksrt-cap-matrix__cap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.aksrt-cap-matrix__cap code {
|
||||
font-size: 11px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
.aksrt-cap-matrix__empty {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.aksrt-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user