fix: enforce platform role menu access

This commit is contained in:
2026-07-27 12:48:35 +08:00
parent cd58e1f6b6
commit 160172bf10
33 changed files with 481 additions and 107 deletions

View File

@@ -39,6 +39,9 @@
<a-date-picker v-else-if="field.type === 'datetime'" v-model="form[field.key]" show-time value-format="YYYY-MM-DDTHH:mm:ssZ" />
<a-textarea v-else-if="field.type === 'json' || field.type === 'textarea'" v-model="form[field.key]" :auto-size="{ minRows: 3, maxRows: 8 }" />
<a-input-password v-else-if="field.type === 'password'" v-model="form[field.key]" />
<a-select v-else-if="field.type === 'role-code'" v-model="form[field.key]">
<a-option v-for="role in roleOptions" :key="role.role_code" :value="role.role_code">{{ role.name }}</a-option>
</a-select>
<a-input v-else v-model="form[field.key]" :placeholder="`请输入${field.label}`" />
</a-form-item>
</a-form>
@@ -59,6 +62,9 @@
<a-input-number v-if="field.type === 'number'" v-model="actionForm[field.key]" />
<a-switch v-else-if="field.type === 'boolean'" v-model="actionForm[field.key]" />
<a-textarea v-else-if="field.type === 'json' || field.type === 'textarea'" v-model="actionForm[field.key]" />
<a-select v-else-if="field.type === 'menu-identities'" v-model="actionForm[field.key]" multiple allow-search>
<a-option v-for="menu in menuOptions" :key="menu.identity" :value="menu.identity">{{ menu.name }}</a-option>
</a-select>
<a-input v-else v-model="actionForm[field.key]" />
</a-form-item>
</a-form>
@@ -69,6 +75,7 @@
import { Message, Modal } from '@arco-design/web-vue';
import { computed, onMounted, reactive, ref } from 'vue';
import { resourceApi } from '@/api/resource';
import { platformApi, type PlatformMenu, type PlatformRole } from '@/api/platform';
import {
buildResourcePayload,
isMissingField,
@@ -92,6 +99,8 @@ const detail = ref<Row>({});
const actionVisible = ref(false);
const activeAction = ref<DetailAction>();
const actionForm = reactive<Record<string, any>>({});
const menuOptions = ref<PlatformMenu[]>([]);
const roleOptions = ref<PlatformRole[]>([]);
const canCreate = computed(() => props.definition.mode !== 'readonly');
const canEdit = computed(() => props.definition.mode === 'writable');
const canArchive = computed(() => props.definition.mode === 'writable');
@@ -166,9 +175,23 @@ async function openDetail(row: Row) {
}
}
function openDetailAction(action: DetailAction) {
async function openDetailAction(action: DetailAction) {
activeAction.value = action;
for (const field of action.fields) actionForm[field.key] = undefined;
if (action.fields.some((field) => field.type === 'menu-identities')) {
try {
const identity = String(detail.value.identity);
const [menus, assigned] = await Promise.all([
platformApi.listMenu(),
platformApi.listRoleMenuIdentities(identity),
]);
menuOptions.value = menus.list;
actionForm.menu_identities = assigned.menu_identities;
} catch (error) {
Message.error((error as Error).message);
return;
}
}
actionVisible.value = true;
}
@@ -188,10 +211,17 @@ async function submitDetailAction() {
...action.payload,
...buildResourcePayload(action.fields, actionForm),
};
await resourceApi.create(
action.resource.replace(':identity', String(detail.value.identity)),
payload,
);
if (action.fields.some((field) => field.type === 'menu-identities')) {
await platformApi.replaceRoleMenus(
String(detail.value.identity),
payload.menu_identities as string[],
);
} else {
await resourceApi.create(
action.resource.replace(':identity', String(detail.value.identity)),
payload,
);
}
Message.success('操作成功');
actionVisible.value = false;
await openDetail(detail.value);
@@ -256,7 +286,15 @@ async function changePage(next: number) {
await load();
}
onMounted(load);
onMounted(async () => {
await load();
if (props.definition.fields.some((field) => field.type === 'role-code')) {
const result = await platformApi.listRole();
roleOptions.value = result.list.filter(
(role) => !role.is_system && role.status === 'enabled',
);
}
});
</script>
<style scoped lang="less">