fix: c
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
@@ -25,6 +25,37 @@ export namespace FeatureApi {
|
||||
api_id?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface FeatureExampleItem {
|
||||
id: number;
|
||||
feature_id: number;
|
||||
api_id: string;
|
||||
data: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface ConfigFeatureExampleRequest {
|
||||
feature_id: number;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface ConfigFeatureExampleResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface GetFeatureExampleRequest {
|
||||
feature_id: number;
|
||||
}
|
||||
|
||||
export interface GetFeatureExampleResponse {
|
||||
id: number;
|
||||
feature_id: number;
|
||||
api_id: string;
|
||||
data: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,10 +103,35 @@ async function deleteFeature(id: number) {
|
||||
return requestClient.delete<{ success: boolean }>(`/feature/delete/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置功能示例数据
|
||||
* @param data 示例数据配置
|
||||
*/
|
||||
async function configFeatureExample(
|
||||
data: FeatureApi.ConfigFeatureExampleRequest,
|
||||
) {
|
||||
return requestClient.post<FeatureApi.ConfigFeatureExampleResponse>(
|
||||
'/feature/config-example',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取功能示例数据
|
||||
* @param featureId 功能ID
|
||||
*/
|
||||
async function getFeatureExample(featureId: number) {
|
||||
return requestClient.get<FeatureApi.GetFeatureExampleResponse>(
|
||||
`/feature/example/${featureId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
configFeatureExample,
|
||||
createFeature,
|
||||
deleteFeature,
|
||||
getFeatureDetail,
|
||||
getFeatureExample,
|
||||
getFeatureList,
|
||||
updateFeature,
|
||||
};
|
||||
|
||||
165
apps/web-antd/src/api/system/api.ts
Normal file
165
apps/web-antd/src/api/system/api.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace SystemApiApi {
|
||||
export interface SystemApiItem {
|
||||
id: number;
|
||||
role_id?: number;
|
||||
api_id?: number;
|
||||
api_name: string;
|
||||
api_code: string;
|
||||
method: string;
|
||||
url: string;
|
||||
status: 0 | 1;
|
||||
description?: string;
|
||||
create_time?: string;
|
||||
update_time?: string;
|
||||
}
|
||||
|
||||
export interface SystemApi {
|
||||
list: SystemApiItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface SystemApiAllResponse {
|
||||
items: SystemApiItem[];
|
||||
}
|
||||
|
||||
export interface SystemRoleApiResponse {
|
||||
items: null | SystemApiItem[];
|
||||
}
|
||||
|
||||
export interface RoleApiItem {
|
||||
id: number;
|
||||
role_id: number;
|
||||
api_id: number;
|
||||
api_name: string;
|
||||
api_code: string;
|
||||
method: string;
|
||||
url: string;
|
||||
status: 0 | 1;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface RoleApi {
|
||||
list: RoleApiItem[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API列表数据
|
||||
*/
|
||||
async function getApiList(params: Recordable<any>) {
|
||||
return requestClient.get<SystemApiApi.SystemApi>('/api/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API详情
|
||||
* @param id API ID
|
||||
*/
|
||||
async function getApiDetail(id: number) {
|
||||
return requestClient.get<SystemApiApi.SystemApiItem>(`/api/detail/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建API
|
||||
* @param data API数据
|
||||
*/
|
||||
async function createApi(
|
||||
data: Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>,
|
||||
) {
|
||||
return requestClient.post('/api/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新API
|
||||
* @param id API ID
|
||||
* @param data API数据
|
||||
*/
|
||||
async function updateApi(
|
||||
id: number,
|
||||
data: Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>,
|
||||
) {
|
||||
return requestClient.put(`/api/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除API
|
||||
* @param id API ID
|
||||
*/
|
||||
async function deleteApi(id: number) {
|
||||
return requestClient.delete(`/api/delete/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新API状态
|
||||
* @param data.ids API ID数组
|
||||
* @param data.status 状态值
|
||||
*/
|
||||
async function batchUpdateApiStatus(data: { ids: number[]; status: 0 | 1 }) {
|
||||
return requestClient.put('/api/batch-update-status', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色API权限列表
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
async function getRoleApiList(roleId: number) {
|
||||
return requestClient.get<SystemApiApi.SystemRoleApiResponse>(
|
||||
`/role/${roleId}/api/list`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配角色API权限
|
||||
* @param data.api_ids API ID数组
|
||||
* @param data.role_id 角色ID
|
||||
*/
|
||||
async function assignRoleApi(data: { api_ids: number[]; role_id: number }) {
|
||||
return requestClient.post('/role/api/assign', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除角色API权限
|
||||
* @param data.api_ids API ID数组
|
||||
* @param data.role_id 角色ID
|
||||
*/
|
||||
async function removeRoleApi(data: { api_ids: number[]; role_id: number }) {
|
||||
return requestClient.post('/role/api/remove', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色API权限(全量更新)
|
||||
* @param data.api_ids API ID数组
|
||||
* @param data.role_id 角色ID
|
||||
*/
|
||||
async function updateRoleApi(data: { api_ids: number[]; role_id: number }) {
|
||||
return requestClient.put('/role/api/update', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有API列表(用于权限分配)
|
||||
* @param params.status 状态过滤
|
||||
*/
|
||||
async function getAllApiList(params?: { status?: number }) {
|
||||
return requestClient.get<SystemApiApi.SystemApiAllResponse>('/api/all', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
assignRoleApi,
|
||||
batchUpdateApiStatus,
|
||||
createApi,
|
||||
deleteApi,
|
||||
getAllApiList,
|
||||
getApiDetail,
|
||||
getApiList,
|
||||
getRoleApiList,
|
||||
removeRoleApi,
|
||||
updateApi,
|
||||
updateRoleApi,
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './api';
|
||||
export * from './dept';
|
||||
export * from './menu';
|
||||
export * from './role';
|
||||
|
||||
@@ -51,4 +51,14 @@ async function deleteUser(id: string) {
|
||||
return requestClient.delete(`/user/delete/${id}`);
|
||||
}
|
||||
|
||||
export { createUser, deleteUser, getUserList, updateUser };
|
||||
/**
|
||||
* 重置用户密码
|
||||
* @param id 用户 ID
|
||||
* @param data 新密码数据
|
||||
* @param data.password 新密码
|
||||
*/
|
||||
async function resetPassword(id: string, data: { password: string }) {
|
||||
return requestClient.post(`/reset-password/${id}`, data);
|
||||
}
|
||||
|
||||
export { createUser, deleteUser, getUserList, resetPassword, updateUser };
|
||||
|
||||
@@ -61,10 +61,27 @@
|
||||
"operation": "Operation",
|
||||
"permissions": "Permissions",
|
||||
"setPermissions": "Permissions",
|
||||
"setApiPermissions": "API Permissions",
|
||||
"roleCode": "Role Code",
|
||||
"description": "Description"
|
||||
},
|
||||
"api": {
|
||||
"title": "API Management",
|
||||
"list": "API List",
|
||||
"name": "API",
|
||||
"apiName": "API Name",
|
||||
"apiCode": "API Code",
|
||||
"method": "Request Method",
|
||||
"url": "API URL",
|
||||
"status": "Status",
|
||||
"description": "Description",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation",
|
||||
"permissions": "Permissions",
|
||||
"setPermissions": "Set Permissions"
|
||||
},
|
||||
"user": {
|
||||
"title": "User Management",
|
||||
"name": "User",
|
||||
"list": "User List",
|
||||
"userName": "Username",
|
||||
@@ -72,6 +89,11 @@
|
||||
"status": "Status",
|
||||
"setPermissions": "Set Permissions",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation"
|
||||
"operation": "Operation",
|
||||
"resetPassword": "Reset Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"confirmPasswordRequired": "Please confirm password",
|
||||
"passwordMismatch": "The two passwords do not match"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +62,28 @@
|
||||
"operation": "操作",
|
||||
"permissions": "权限",
|
||||
"setPermissions": "授权",
|
||||
"setApiPermissions": "API权限",
|
||||
"roleCode": "角色编号",
|
||||
"description": "描述"
|
||||
},
|
||||
"title": "系统管理",
|
||||
"api": {
|
||||
"title": "API管理",
|
||||
"list": "API列表",
|
||||
"name": "API",
|
||||
"apiName": "API名称",
|
||||
"apiCode": "API编码",
|
||||
"method": "请求方法",
|
||||
"url": "API地址",
|
||||
"status": "状态",
|
||||
"description": "描述",
|
||||
"createTime": "创建时间",
|
||||
"operation": "操作",
|
||||
"permissions": "权限",
|
||||
"setPermissions": "设置权限"
|
||||
},
|
||||
"user": {
|
||||
"title": "用户管理",
|
||||
"name": "用户",
|
||||
"list": "用户列表",
|
||||
"userName": "用户名",
|
||||
@@ -74,6 +91,11 @@
|
||||
"status": "状态",
|
||||
"setPermissions": "设置权限",
|
||||
"createTime": "创建时间",
|
||||
"operation": "操作"
|
||||
"operation": "操作",
|
||||
"resetPassword": "重置密码",
|
||||
"newPassword": "新密码",
|
||||
"confirmPassword": "确认密码",
|
||||
"confirmPasswordRequired": "请确认密码",
|
||||
"passwordMismatch": "两次输入的密码不一致"
|
||||
}
|
||||
}
|
||||
|
||||
64
apps/web-antd/src/router/routes/modules/system.ts
Normal file
64
apps/web-antd/src/router/routes/modules/system.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'ion:settings-outline',
|
||||
order: 9997,
|
||||
title: $t('system.title'),
|
||||
},
|
||||
name: 'System',
|
||||
path: '/system',
|
||||
children: [
|
||||
{
|
||||
path: '/system/user',
|
||||
name: 'SystemUser',
|
||||
meta: {
|
||||
icon: 'mdi:account',
|
||||
title: $t('system.user.title'),
|
||||
},
|
||||
component: () => import('#/views/system/user/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/role',
|
||||
name: 'SystemRole',
|
||||
meta: {
|
||||
icon: 'mdi:account-group',
|
||||
title: $t('system.role.title'),
|
||||
},
|
||||
component: () => import('#/views/system/role/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/api',
|
||||
name: 'SystemApi',
|
||||
meta: {
|
||||
icon: 'mdi:api',
|
||||
title: $t('system.api.title'),
|
||||
},
|
||||
component: () => import('#/views/system/api/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/menu',
|
||||
name: 'SystemMenu',
|
||||
meta: {
|
||||
icon: 'mdi:menu',
|
||||
title: $t('system.menu.title'),
|
||||
},
|
||||
component: () => import('#/views/system/menu/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/dept',
|
||||
name: 'SystemDept',
|
||||
meta: {
|
||||
icon: 'charm:organisation',
|
||||
title: $t('system.dept.title'),
|
||||
},
|
||||
component: () => import('#/views/system/dept/list.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
@@ -69,12 +69,26 @@ export function useColumns<T = FeatureApi.FeatureItem>(
|
||||
nameTitle: '模块',
|
||||
onClick: onActionClick,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
code: 'edit',
|
||||
text: '编辑',
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
text: '删除',
|
||||
},
|
||||
{
|
||||
code: 'example',
|
||||
text: '示例配置',
|
||||
},
|
||||
],
|
||||
name: 'CellOperation',
|
||||
},
|
||||
field: 'operation',
|
||||
fixed: 'right',
|
||||
title: '操作',
|
||||
width: 130,
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { FeatureApi } from '#/api/product-manage';
|
||||
|
||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
||||
import { Plus } from '@vben/icons';
|
||||
|
||||
import { Button, message } from 'ant-design-vue';
|
||||
@@ -14,6 +14,7 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteFeature, getFeatureList } from '#/api/product-manage';
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import ExampleConfig from './modules/example-config.vue';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
// 表单抽屉
|
||||
@@ -22,6 +23,12 @@ const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 示例配置弹窗
|
||||
const [ExampleConfigModal, exampleConfigModalApi] = useVbenModal({
|
||||
connectedComponent: ExampleConfig,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
@@ -67,6 +74,10 @@ function onActionClick(e: OnActionClickParams<FeatureApi.FeatureItem>) {
|
||||
onEdit(e.row);
|
||||
break;
|
||||
}
|
||||
case 'example': {
|
||||
onExampleConfig(e.row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +115,17 @@ function onRefresh() {
|
||||
function onCreate() {
|
||||
formDrawerApi.setData({}).open();
|
||||
}
|
||||
|
||||
// 示例配置处理
|
||||
function onExampleConfig(row: FeatureApi.FeatureItem) {
|
||||
exampleConfigModalApi.setData(row).open();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<ExampleConfigModal @success="onRefresh" />
|
||||
<Grid table-title="模块列表">
|
||||
<template #toolbar-tools>
|
||||
<Button type="primary" @click="onCreate">
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
<script lang="ts" setup>
|
||||
import type { FeatureApi } from '#/api/product-manage';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Input, message } from 'ant-design-vue';
|
||||
|
||||
import { configFeatureExample, getFeatureExample } from '#/api/product-manage';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const featureData = ref<FeatureApi.FeatureItem>();
|
||||
const exampleData = ref('');
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
async onConfirm() {
|
||||
if (!featureData.value) return;
|
||||
|
||||
saving.value = true;
|
||||
try {
|
||||
await configFeatureExample({
|
||||
feature_id: featureData.value.id,
|
||||
data: exampleData.value,
|
||||
});
|
||||
message.success('示例配置保存成功');
|
||||
emit('success');
|
||||
modalApi.close();
|
||||
} catch (error) {
|
||||
console.error('保存示例配置失败:', error);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
const data = modalApi.getData<FeatureApi.FeatureItem>();
|
||||
if (data) {
|
||||
featureData.value = data;
|
||||
loadExampleData();
|
||||
}
|
||||
} else {
|
||||
featureData.value = undefined;
|
||||
exampleData.value = '';
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// 加载示例数据
|
||||
async function loadExampleData() {
|
||||
if (!featureData.value) return;
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getFeatureExample(featureData.value.id);
|
||||
exampleData.value = response.data || '';
|
||||
} catch (error) {
|
||||
console.error('获取示例数据失败:', error);
|
||||
exampleData.value = '';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化JSON
|
||||
function formatJson() {
|
||||
try {
|
||||
if (exampleData.value) {
|
||||
const parsed = JSON.parse(exampleData.value);
|
||||
exampleData.value = JSON.stringify(parsed, null, 2);
|
||||
}
|
||||
} catch {
|
||||
message.error('JSON格式错误,无法格式化');
|
||||
}
|
||||
}
|
||||
|
||||
// 清空数据
|
||||
function clearData() {
|
||||
exampleData.value = '';
|
||||
}
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
return featureData.value
|
||||
? `示例配置 - ${featureData.value.name}`
|
||||
: '示例配置';
|
||||
});
|
||||
|
||||
const isJsonValid = computed(() => {
|
||||
if (!exampleData.value) return true;
|
||||
try {
|
||||
JSON.parse(exampleData.value);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="modalTitle" class="w-[1200px]">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<label class="text-sm font-medium">示例数据 (JSON格式)</label>
|
||||
<div class="space-x-2">
|
||||
<Button size="small" @click="formatJson">格式化</Button>
|
||||
<Button size="small" @click="clearData">清空</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Input.TextArea
|
||||
v-model:value="exampleData"
|
||||
:loading="loading"
|
||||
:rows="15"
|
||||
:status="isJsonValid ? undefined : 'error'"
|
||||
placeholder="请输入JSON格式的示例数据..."
|
||||
class="font-mono text-sm"
|
||||
/>
|
||||
<div v-if="!isJsonValid" class="mt-1 text-xs text-red-500">
|
||||
JSON格式错误,请检查语法
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-gray-500">
|
||||
<p>提示:</p>
|
||||
<ul class="list-disc space-y-1 pl-4">
|
||||
<li>如果当前功能没有配置示例数据,输入框将为空</li>
|
||||
<li>可以在此输入JSON格式的示例数据</li>
|
||||
<li>点击"格式化"按钮可以美化JSON格式</li>
|
||||
<li>保存后该示例数据将用于功能展示</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end space-x-2">
|
||||
<Button @click="modalApi.close">取消</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
:disabled="!isJsonValid"
|
||||
@click="modalApi.onConfirm"
|
||||
>
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
178
apps/web-antd/src/views/system/api/data.ts
Normal file
178
apps/web-antd/src/views/system/api/data.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
import type { SystemApiApi } from '#/api';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'api_name',
|
||||
label: $t('system.api.apiName'),
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'api_code',
|
||||
label: $t('system.api.apiCode'),
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
class: 'w-full',
|
||||
placeholder: '请选择请求方法',
|
||||
options: [
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
{ label: 'PUT', value: 'PUT' },
|
||||
{ label: 'DELETE', value: 'DELETE' },
|
||||
],
|
||||
},
|
||||
fieldName: 'method',
|
||||
label: $t('system.api.method'),
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'url',
|
||||
label: $t('system.api.url'),
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'RadioGroup',
|
||||
componentProps: {
|
||||
buttonStyle: 'solid',
|
||||
options: [
|
||||
{ label: $t('common.enabled'), value: 1 },
|
||||
{ label: $t('common.disabled'), value: 0 },
|
||||
],
|
||||
optionType: 'button',
|
||||
},
|
||||
defaultValue: 1,
|
||||
fieldName: 'status',
|
||||
label: $t('system.api.status'),
|
||||
},
|
||||
{
|
||||
component: 'Textarea',
|
||||
fieldName: 'description',
|
||||
label: $t('system.api.description'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'api_name',
|
||||
label: $t('system.api.apiName'),
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'api_code',
|
||||
label: $t('system.api.apiCode'),
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
class: 'w-full',
|
||||
options: [
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
{ label: 'PUT', value: 'PUT' },
|
||||
{ label: 'DELETE', value: 'DELETE' },
|
||||
],
|
||||
},
|
||||
fieldName: 'method',
|
||||
label: $t('system.api.method'),
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
class: 'w-full',
|
||||
options: [
|
||||
{ label: $t('common.enabled'), value: 1 },
|
||||
{ label: $t('common.disabled'), value: 0 },
|
||||
],
|
||||
},
|
||||
fieldName: 'status',
|
||||
label: $t('system.api.status'),
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'create_time',
|
||||
label: $t('system.api.createTime'),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function useColumns<T = SystemApiApi.SystemApiItem>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
onStatusChange?: (newStatus: any, row: T) => PromiseLike<boolean | undefined>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'api_name',
|
||||
title: $t('system.api.apiName'),
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'api_code',
|
||||
title: $t('system.api.apiCode'),
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'method',
|
||||
title: $t('system.api.method'),
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
title: $t('system.api.url'),
|
||||
minWidth: 200,
|
||||
},
|
||||
{
|
||||
cellRender: {
|
||||
attrs: { beforeChange: onStatusChange },
|
||||
name: onStatusChange ? 'CellSwitch' : 'CellTag',
|
||||
},
|
||||
field: 'status',
|
||||
title: $t('system.api.status'),
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
minWidth: 150,
|
||||
title: $t('system.api.description'),
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: $t('system.api.createTime'),
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
cellRender: {
|
||||
attrs: {
|
||||
nameField: 'api_name',
|
||||
nameTitle: $t('system.api.apiName'),
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
'edit', // 默认的编辑按钮
|
||||
'delete', // 默认的删除按钮
|
||||
],
|
||||
},
|
||||
field: 'operation',
|
||||
fixed: 'right',
|
||||
title: $t('system.api.operation'),
|
||||
width: 130,
|
||||
},
|
||||
];
|
||||
}
|
||||
171
apps/web-antd/src/views/system/api/list.vue
Normal file
171
apps/web-antd/src/views/system/api/list.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { SystemApiApi } from '#/api';
|
||||
|
||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||
import { Plus } from '@vben/icons';
|
||||
|
||||
import { Button, message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteApi, getApiList, updateApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
fieldMappingTime: [['create_time', ['startTime', 'endTime']]],
|
||||
schema: useGridFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useColumns(onActionClick, onStatusChange),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getApiList({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'list',
|
||||
total: 'total',
|
||||
},
|
||||
autoLoad: true,
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
custom: true,
|
||||
export: false,
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
zoom: true,
|
||||
},
|
||||
} as VxeTableGridOptions<SystemApiApi.SystemApiItem>,
|
||||
});
|
||||
|
||||
function onActionClick(e: OnActionClickParams<SystemApiApi.SystemApiItem>) {
|
||||
switch (e.code) {
|
||||
case 'delete': {
|
||||
onDelete(e.row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(e.row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Antd的Modal.confirm封装为promise,方便在异步函数中调用。
|
||||
* @param content 提示内容
|
||||
* @param title 提示标题
|
||||
*/
|
||||
function confirm(content: string, title: string) {
|
||||
return new Promise((reslove, reject) => {
|
||||
Modal.confirm({
|
||||
content,
|
||||
onCancel() {
|
||||
reject(new Error('已取消'));
|
||||
},
|
||||
onOk() {
|
||||
reslove(true);
|
||||
},
|
||||
title,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 状态开关即将改变
|
||||
* @param newStatus 期望改变的状态值
|
||||
* @param row 行数据
|
||||
* @returns 返回false则中止改变,返回其他值(undefined、true)则允许改变
|
||||
*/
|
||||
async function onStatusChange(
|
||||
newStatus: number,
|
||||
row: SystemApiApi.SystemApiItem,
|
||||
) {
|
||||
const status: Recordable<string> = {
|
||||
0: '禁用',
|
||||
1: '启用',
|
||||
};
|
||||
try {
|
||||
await confirm(
|
||||
`你要将${row.api_name}的状态切换为 【${status[newStatus.toString()]}】 吗?`,
|
||||
`切换状态`,
|
||||
);
|
||||
await updateApi(row.id, {
|
||||
...row,
|
||||
status: newStatus as 0 | 1,
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function onEdit(row: SystemApiApi.SystemApiItem) {
|
||||
formDrawerApi.setData(row).open();
|
||||
}
|
||||
|
||||
function onDelete(row: SystemApiApi.SystemApiItem) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.api_name]),
|
||||
duration: 0,
|
||||
key: 'action_process_msg',
|
||||
});
|
||||
deleteApi(row.id)
|
||||
.then(() => {
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.api_name]),
|
||||
key: 'action_process_msg',
|
||||
});
|
||||
onRefresh();
|
||||
})
|
||||
.catch(() => {
|
||||
hideLoading();
|
||||
});
|
||||
}
|
||||
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
function onCreate() {
|
||||
formDrawerApi.setData({}).open();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<Grid :table-title="$t('system.api.list')">
|
||||
<template #toolbar-tools>
|
||||
<Button type="primary" @click="onCreate">
|
||||
<Plus class="size-5" />
|
||||
{{ $t('ui.actionTitle.create', [$t('system.api.name')]) }}
|
||||
</Button>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
77
apps/web-antd/src/views/system/api/modules/form.vue
Normal file
77
apps/web-antd/src/views/system/api/modules/form.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SystemApiApi } from '#/api';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
|
||||
|
||||
import { createApi, updateApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const formData = ref<SystemApiApi.SystemApiItem>();
|
||||
const schema = useFormSchema();
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
colon: true,
|
||||
formItemClass: 'col-span-2 md:col-span-1',
|
||||
},
|
||||
schema,
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2 gap-x-4',
|
||||
});
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
onConfirm: onSubmit,
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<SystemApiApi.SystemApiItem>();
|
||||
if (data) {
|
||||
formData.value = data;
|
||||
formApi.setValues(formData.value);
|
||||
} else {
|
||||
formApi.resetForm();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (valid) {
|
||||
drawerApi.lock();
|
||||
const data =
|
||||
await formApi.getValues<
|
||||
Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>
|
||||
>();
|
||||
|
||||
try {
|
||||
await (formData.value?.id
|
||||
? updateApi(formData.value.id, data)
|
||||
: createApi(data));
|
||||
drawerApi.close();
|
||||
emit('success');
|
||||
} finally {
|
||||
drawerApi.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getDrawerTitle = computed(() =>
|
||||
formData.value?.id
|
||||
? $t('ui.actionTitle.edit', [$t('system.api.name')])
|
||||
: $t('ui.actionTitle.create', [$t('system.api.name')]),
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer class="w-full max-w-[600px]" :title="getDrawerTitle">
|
||||
<Form class="mx-4" />
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -80,7 +80,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function useColumns<T = SystemRoleApi.SystemRole>(
|
||||
export function useColumns<T = SystemRoleApi.SystemRoleItem>(
|
||||
onActionClick: OnActionClickFn<T>,
|
||||
onStatusChange?: (newStatus: any, row: T) => PromiseLike<boolean | undefined>,
|
||||
): VxeTableGridOptions['columns'] {
|
||||
@@ -123,11 +123,26 @@ export function useColumns<T = SystemRoleApi.SystemRole>(
|
||||
onClick: onActionClick,
|
||||
},
|
||||
name: 'CellOperation',
|
||||
options: [
|
||||
{
|
||||
code: 'edit',
|
||||
text: $t('ui.actionTitle.edit', [$t('system.role.name')]),
|
||||
},
|
||||
{
|
||||
code: 'api-permissions',
|
||||
text: $t('system.role.setApiPermissions'),
|
||||
},
|
||||
{
|
||||
code: 'delete',
|
||||
text: $t('ui.actionTitle.delete', [$t('system.role.name')]),
|
||||
danger: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
field: 'operation',
|
||||
fixed: 'right',
|
||||
title: $t('system.role.operation'),
|
||||
width: 130,
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { deleteRole, getRoleList, updateRole } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import ApiPermissions from './modules/api-permissions.vue';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
@@ -24,6 +25,11 @@ const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [ApiPermissionsDrawer, apiPermissionsDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: ApiPermissions,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
fieldMappingTime: [['create_time', ['startTime', 'endTime']]],
|
||||
@@ -56,11 +62,15 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
search: true,
|
||||
zoom: true,
|
||||
},
|
||||
} as VxeTableGridOptions<SystemRoleApi.SystemRole>,
|
||||
} as VxeTableGridOptions<SystemRoleApi.SystemRoleItem>,
|
||||
});
|
||||
|
||||
function onActionClick(e: OnActionClickParams<SystemRoleApi.SystemRole>) {
|
||||
function onActionClick(e: OnActionClickParams<SystemRoleApi.SystemRoleItem>) {
|
||||
switch (e.code) {
|
||||
case 'api-permissions': {
|
||||
onApiPermissions(e.row);
|
||||
break;
|
||||
}
|
||||
case 'delete': {
|
||||
onDelete(e.row);
|
||||
break;
|
||||
@@ -118,11 +128,11 @@ async function onStatusChange(
|
||||
}
|
||||
}
|
||||
|
||||
function onEdit(row: SystemRoleApi.SystemRole) {
|
||||
function onEdit(row: SystemRoleApi.SystemRoleItem) {
|
||||
formDrawerApi.setData(row).open();
|
||||
}
|
||||
|
||||
function onDelete(row: SystemRoleApi.SystemRole) {
|
||||
function onDelete(row: SystemRoleApi.SystemRoleItem) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.role_name]),
|
||||
duration: 0,
|
||||
@@ -148,10 +158,15 @@ function onRefresh() {
|
||||
function onCreate() {
|
||||
formDrawerApi.setData({}).open();
|
||||
}
|
||||
|
||||
function onApiPermissions(row: SystemRoleApi.SystemRoleItem) {
|
||||
apiPermissionsDrawerApi.setData(row).open();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<ApiPermissionsDrawer @success="onRefresh" />
|
||||
<Grid :table-title="$t('system.role.list')">
|
||||
<template #toolbar-tools>
|
||||
<Button type="primary" @click="onCreate">
|
||||
|
||||
214
apps/web-antd/src/views/system/role/modules/api-permissions.vue
Normal file
214
apps/web-antd/src/views/system/role/modules/api-permissions.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SystemRoleApi } from '#/api';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
import { Button, Checkbox, message, Spin } from 'ant-design-vue';
|
||||
|
||||
import { getAllApiList, getRoleApiList, updateRoleApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const emits = defineEmits(['success']);
|
||||
|
||||
const loading = ref(false);
|
||||
const allApiList = ref<any[]>([]);
|
||||
const roleApiList = ref<any[]>([]);
|
||||
const selectedApiIds = ref<number[]>([]);
|
||||
const formData = ref<SystemRoleApi.SystemRoleItem>();
|
||||
const roleId = ref<number>();
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
async onConfirm() {
|
||||
await onSave();
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<SystemRoleApi.SystemRoleItem>();
|
||||
if (data) {
|
||||
formData.value = data;
|
||||
roleId.value = data.id;
|
||||
fetchRoleApiList();
|
||||
}
|
||||
fetchAllApiList();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// 计算已选中的API ID
|
||||
const selectedApiIdsSet = computed(() => new Set(selectedApiIds.value));
|
||||
|
||||
// 获取所有API列表
|
||||
async function fetchAllApiList() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await getAllApiList({ status: 1 }); // 只获取启用的API
|
||||
allApiList.value = response.items || [];
|
||||
} catch (error) {
|
||||
console.error('获取API列表失败:', error);
|
||||
message.error('获取API列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色已分配的API权限
|
||||
async function fetchRoleApiList() {
|
||||
if (!roleId.value) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await getRoleApiList(roleId.value);
|
||||
roleApiList.value = response.items || [];
|
||||
selectedApiIds.value = roleApiList.value.map((item) => item.api_id);
|
||||
} catch (error) {
|
||||
console.error('获取角色API权限失败:', error);
|
||||
message.error('获取角色API权限失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 全选/取消全选
|
||||
function toggleSelectAll(e: any) {
|
||||
const checked = e.target.checked;
|
||||
selectedApiIds.value = checked
|
||||
? allApiList.value.map((api) => api.api_id)
|
||||
: [];
|
||||
}
|
||||
|
||||
// 判断是否全选
|
||||
const isAllSelected = computed(() => {
|
||||
return (
|
||||
allApiList.value.length > 0 &&
|
||||
selectedApiIds.value.length === allApiList.value.length
|
||||
);
|
||||
});
|
||||
|
||||
// 判断是否部分选中
|
||||
const isIndeterminate = computed(() => {
|
||||
return (
|
||||
selectedApiIds.value.length > 0 &&
|
||||
selectedApiIds.value.length < allApiList.value.length
|
||||
);
|
||||
});
|
||||
|
||||
// 保存API权限
|
||||
async function onSave() {
|
||||
if (!roleId.value) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
await updateRoleApi({
|
||||
role_id: roleId.value,
|
||||
api_ids: selectedApiIds.value,
|
||||
});
|
||||
message.success('API权限保存成功');
|
||||
emits('success');
|
||||
drawerApi.close();
|
||||
} catch (error) {
|
||||
console.error('保存API权限失败:', error);
|
||||
message.error('保存API权限失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 重置选择
|
||||
function onReset() {
|
||||
selectedApiIds.value = roleApiList.value.map((item) => item.api_id);
|
||||
}
|
||||
|
||||
// 计算抽屉标题
|
||||
const getDrawerTitle = computed(() => {
|
||||
return formData.value?.role_name
|
||||
? `${$t('system.role.setApiPermissions')} - ${formData.value.role_name}`
|
||||
: $t('system.role.setApiPermissions');
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer :title="getDrawerTitle">
|
||||
<div class="p-4">
|
||||
<div class="mb-4">
|
||||
<p class="text-sm text-gray-500">
|
||||
为角色分配API访问权限,勾选的API将被允许访问
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Spin :spinning="loading">
|
||||
<div class="space-y-4">
|
||||
<!-- 全选操作 -->
|
||||
<div class="flex items-center gap-4 rounded-lg bg-gray-50 p-3">
|
||||
<Checkbox
|
||||
:checked="isAllSelected"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="toggleSelectAll"
|
||||
>
|
||||
全选
|
||||
</Checkbox>
|
||||
<span class="text-sm text-gray-500">
|
||||
已选择 {{ selectedApiIds.length }} / {{ allApiList.length }} 个API
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- API列表 -->
|
||||
<div class="max-h-96 overflow-y-auto rounded-lg border">
|
||||
<div
|
||||
v-for="api in allApiList"
|
||||
:key="api.api_id"
|
||||
class="flex items-center gap-3 border-b p-3 last:border-b-0 hover:bg-gray-50"
|
||||
>
|
||||
<Checkbox
|
||||
:checked="selectedApiIdsSet.has(api.api_id)"
|
||||
@change="
|
||||
(e) => {
|
||||
if (e.target.checked) {
|
||||
selectedApiIds.push(api.api_id);
|
||||
} else {
|
||||
const index = selectedApiIds.indexOf(api.api_id);
|
||||
if (index > -1) {
|
||||
selectedApiIds.splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
"
|
||||
/>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium">{{ api.api_name }}</span>
|
||||
<span
|
||||
class="rounded px-2 py-1 text-xs"
|
||||
:class="{
|
||||
'bg-blue-100 text-blue-800': api.method === 'GET',
|
||||
'bg-green-100 text-green-800': api.method === 'POST',
|
||||
'bg-orange-100 text-orange-800': api.method === 'PUT',
|
||||
'bg-red-100 text-red-800': api.method === 'DELETE',
|
||||
}"
|
||||
>
|
||||
{{ api.method }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-gray-500">
|
||||
{{ api.url }}
|
||||
</div>
|
||||
<div v-if="api.description" class="mt-1 text-xs text-gray-400">
|
||||
{{ api.description }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="mt-6 flex justify-end gap-2">
|
||||
<Button @click="onReset"> 重置 </Button>
|
||||
<Button type="primary" @click="onSave" :loading="loading">
|
||||
保存
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -121,12 +121,17 @@ export function useColumns<T = SystemUserApi.SystemUser>(
|
||||
nameTitle: $t('system.user.userName'),
|
||||
onClick: onActionClick,
|
||||
},
|
||||
options: [
|
||||
{ code: 'edit', text: '编辑' },
|
||||
{ code: 'resetPassword', text: '重置密码' },
|
||||
{ code: 'delete', text: '删除' },
|
||||
],
|
||||
name: 'CellOperation',
|
||||
},
|
||||
field: 'operation',
|
||||
fixed: 'right',
|
||||
title: $t('system.user.operation'),
|
||||
width: 130,
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -18,12 +18,18 @@ import { $t } from '#/locales';
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
import ResetPasswordForm from './modules/reset-password-form.vue';
|
||||
|
||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [ResetPasswordDrawer, resetPasswordDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: ResetPasswordForm,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
fieldMappingTime: [['create_time', ['startTime', 'endTime']]],
|
||||
@@ -69,6 +75,10 @@ function onActionClick(e: OnActionClickParams<SystemUserApi.SystemUser>) {
|
||||
onEdit(e.row);
|
||||
break;
|
||||
}
|
||||
case 'resetPassword': {
|
||||
onResetPassword(e.row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +132,10 @@ function onEdit(row: SystemUserApi.SystemUser) {
|
||||
formDrawerApi.setData(row).open();
|
||||
}
|
||||
|
||||
function onResetPassword(row: SystemUserApi.SystemUser) {
|
||||
resetPasswordDrawerApi.setData(row).open();
|
||||
}
|
||||
|
||||
function onDelete(row: SystemUserApi.SystemUser) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.username]),
|
||||
@@ -152,6 +166,7 @@ function onCreate() {
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormDrawer />
|
||||
<ResetPasswordDrawer @success="onRefresh" />
|
||||
<Grid :table-title="$t('system.user.list')">
|
||||
<template #toolbar-tools>
|
||||
<Button type="primary" @click="onCreate">
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SystemUserApi } from '#/api/system/user';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer, z } from '@vben/common-ui';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { resetPassword } from '#/api/system/user';
|
||||
|
||||
const emits = defineEmits(['success']);
|
||||
|
||||
const formData = ref<SystemUserApi.SystemUser>();
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
schema: [
|
||||
{
|
||||
component: 'InputPassword',
|
||||
fieldName: 'password',
|
||||
label: '新密码',
|
||||
rules: z.string().min(1, { message: '请输入新密码' }),
|
||||
},
|
||||
{
|
||||
component: 'InputPassword',
|
||||
fieldName: 'confirmPassword',
|
||||
label: '确认密码',
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string()
|
||||
.min(1, { message: '请确认密码' })
|
||||
.refine((value) => value === password, {
|
||||
message: '两次输入的密码不一致',
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
},
|
||||
],
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const id = ref();
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) return;
|
||||
const values = await formApi.getValues();
|
||||
|
||||
drawerApi.lock();
|
||||
resetPassword(id.value, { password: values.password })
|
||||
.then(() => {
|
||||
emits('success');
|
||||
drawerApi.close();
|
||||
})
|
||||
.catch(() => {
|
||||
drawerApi.unlock();
|
||||
});
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<SystemUserApi.SystemUser>();
|
||||
formApi.resetForm();
|
||||
if (data) {
|
||||
formData.value = data;
|
||||
id.value = data.id;
|
||||
} else {
|
||||
id.value = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const getDrawerTitle = computed(() => {
|
||||
return '重置密码';
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<Drawer :title="getDrawerTitle">
|
||||
<Form />
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -11,7 +11,9 @@ export default defineConfig(async () => {
|
||||
rewrite: (path) => path.replace(/^\/api/, ''),
|
||||
// mock代理目标地址
|
||||
// target: 'http://localhost:8888/api',
|
||||
target: 'https://www.tianyuandb.com/api',
|
||||
// target: 'https://www.tianyuandb.com/api',
|
||||
// target: 'https://www.zhinengcha.cn/api',
|
||||
target: 'https://www.quannengcha./api',
|
||||
ws: true,
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user