Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.
Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)
Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
(dropped 3 stale entries, added the 17 missing ones)
Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
when it is empty
Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.
Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
251 lines
6.2 KiB
Vue
251 lines
6.2 KiB
Vue
<template>
|
||
<div class="jio-page-container">
|
||
<PageHeader
|
||
title="基本权限 / 能力矩阵"
|
||
desc="统一管理用户治理、投稿和评论的角色能力矩阵。"
|
||
>
|
||
<template #actions>
|
||
<n-space>
|
||
<n-button quaternary @click="resetDefaults">恢复默认</n-button>
|
||
</n-space>
|
||
</template>
|
||
</PageHeader>
|
||
|
||
<n-alert type="warning" :show-icon="true" style="margin: 12px 0 16px;">
|
||
每项能力都通过“所有人、已登录、等级、VIP、已认证”五列治理;管理员(manage_options)始终可通过。
|
||
</n-alert>
|
||
|
||
|
||
<n-spin :show="loading" style="width: 100%; min-height: 240px;">
|
||
<n-card class="jio-section-card" v-if="summary.cap_count">
|
||
<div class="jio-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="jio-section-card">
|
||
<template #header>
|
||
<div class="jio-cap-matrix__header">
|
||
<div>
|
||
<h3 class="jio-cap-matrix__title">{{ group.title }}</h3>
|
||
<p class="jio-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="jio-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: 'jio-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: 'jio-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>
|
||
.jio-cap-matrix__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.jio-cap-matrix__title {
|
||
margin: 0;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.jio-cap-matrix__desc {
|
||
margin: 4px 0 0;
|
||
font-size: 12px;
|
||
color: var(--jio-color-muted);
|
||
}
|
||
|
||
.jio-cap-matrix__cap {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.jio-cap-matrix__cap code {
|
||
font-size: 11px;
|
||
color: #667085;
|
||
}
|
||
|
||
.jio-cap-matrix__empty {
|
||
color: #c0c4cc;
|
||
}
|
||
|
||
.jio-form-actions {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
margin-top: 16px;
|
||
}
|
||
</style>
|