Files
wordpress-theme-aksrt/assets/admin/src/pages/permissions/PermissionsCapabilities.vue
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
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>
2026-11-01 20:05:04 +08:00

251 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>