新增数据信息 完善数据信息得权限问题
This commit is contained in:
lq1405 2025-05-21 21:27:35 +08:00
parent 896461f6d4
commit 6e52f5ce9a
12 changed files with 810 additions and 14 deletions

View File

@ -83,6 +83,13 @@ export default [
} }
] ]
}, },
{
path: '/optionManagement',
name: 'optionManagement',
icon: 'Product',
access: 'canOptionManagement',
component: './Options/OptionsManagement',
},
{ {
path: '/roleManagement', path: '/roleManagement',
name: 'roleManagement', name: 'roleManagement',

View File

@ -1,6 +1,6 @@
{ {
"name": "lms", "name": "lms",
"version": "1.0.8", "version": "1.1.0",
"private": true, "private": true,
"description": "An out-of-box UI solution for enterprise applications", "description": "An out-of-box UI solution for enterprise applications",
"scripts": { "scripts": {

View File

@ -7,6 +7,7 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
let access = { let access = {
canPrompt: false, canPrompt: false,
canRoleManagement: false, canRoleManagement: false,
canOptionManagement: false,
canUserManagement: false, canUserManagement: false,
canEditUser: false, canEditUser: false,
@ -87,6 +88,7 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
access = { access = {
...access, ...access,
canPrompt: true, canPrompt: true,
canOptionManagement : true,
canUserManagement: true, canUserManagement: true,
canEditUser: true, canEditUser: true,
@ -121,7 +123,8 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
...access, ...access,
canPrompt: true, canPrompt: true,
canRoleManagement: true, canRoleManagement: true,
canOptionManagement : true,
canUserManagement: true, canUserManagement: true,
canEditUser: true, canEditUser: true,
canDeleteUser: true, canDeleteUser: true,

View File

@ -13,6 +13,8 @@ export default {
'menu.roleManagement': '角色管理', 'menu.roleManagement': '角色管理',
'menu.optionManagement': '数据管理',
'menu.userManagement': '用户管理', 'menu.userManagement': '用户管理',
'menu.machineManagement': '机器码管理', 'menu.machineManagement': '机器码管理',

View File

@ -0,0 +1,236 @@
import React, { useEffect, useState } from 'react';
import { Form, Input, Button, Card, Select, message, Spin, FormInstance, SelectProps, Tag } from 'antd';
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons';
import { useParams, history } from 'umi';
import { PageContainer } from '@ant-design/pro-layout';
import { useModel } from '@umijs/max';
import { cusRequest } from '@/request';
import { getOptionCategoryOptions, getOptionTypeOptions, OptionCategory, OptionType } from '@/services/enum/optionEnum';
import { useSoftStore } from '@/store/software';
import { OptionModel } from '@/services/typing/options/option';
import { CheckJsonString } from '@/services/services/common';
import { QueryRoleOption } from '@/services/services/role';
import { isEmpty } from 'lodash';
const { TextArea } = Input;
interface OptionParams {
optionKey?: string;
category?: OptionCategory;
setFormRef: (form: FormInstance) => void;
open: boolean;
}
const AddModifyOption: React.FC<OptionParams> = ({ optionKey, setFormRef, open, category }) => {
const { initialState } = useModel('@@initialState');
const [form] = Form.useForm();
const [loading, setLoading] = useState<boolean>(false);
const [tip, setTip] = useState<string>('加载数据中。。。');
const [roleOptions, setRoleOptions] = useState<SelectProps['options']>([]);
const [messageApi, messageHolder] = message.useMessage();
type TagRender = SelectProps['tagRender'];
const isEdit = !!optionKey;
const tagRender: TagRender = (props) => {
const { label, value, closable, onClose } = props;
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault();
event.stopPropagation();
};
return (
<Tag
color="cyan"
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{ marginInlineEnd: 4 }}
>
{label}
</Tag>
);
};
// 获取选项详情
const fetchOptionDetail = async () => {
debugger;
if (isEmpty(optionKey)) return;
setLoading(true);
setTip('正在获取选项详情,请稍等。。。');
try {
const res = await cusRequest<any>(`/lms/Options/GetAllMessageOptionsByKey/${category}/${optionKey}`, {
method: 'GET',
});
if (res.code === 1) {
form.setFieldsValue(res.data);
} else {
messageApi.error(res.message || '获取选项详情失败');
}
} catch (error) {
console.error('获取选项详情失败:', error);
messageApi.error('获取选项详情失败');
} finally {
setLoading(false);
}
};
// 保存选项
const handleSubmit = async (values: OptionModel.OptionsItem) => {
setLoading(true);
setTip('正在保存数据,请稍等。。。');
try {
// 判断类型和值
if (values.type == OptionType.JSON) {
if (CheckJsonString(values.value) == false) {
messageApi.error("JSON格式不正确请检查");
return;
}
}
if (values.type == OptionType.Number) {
if (isNaN(Number(values.value))) {
messageApi.error("数字格式不正确,请检查");
return;
}
}
if (values.type == OptionType.Boolean) {
if (values.value !== 'true' && values.value !== 'false') {
messageApi.error("布尔值格式不正确,请检查");
return;
}
}
let url = '/lms/Options/AddOptions';
let method = 'POST';
if (isEdit) {
url = `/lms/Options/ModifyOptionsByKey/${optionKey}`;
method = 'POST';
delete values.key;
}
debugger;
if (values.roleNames == null || values.roleNames == undefined) {
values.roleNames = [];
}
const res = await cusRequest<string>(url, {
method,
data: values,
});
console.log(res);
if (res.code == 1) {
messageApi.success(`${isEdit ? '修改' : '添加'}成功`);
} else {
messageApi.error(res.message || `${isEdit ? '修改' : '添加'}失败`);
}
} catch (error) {
console.error(`${isEdit ? '修改' : '添加'}选项失败:`, error);
messageApi.error(`${isEdit ? '修改' : '添加'}失败`);
} finally {
setLoading(false);
}
};
// 获取选项列表
const fetchOptions = async () => {
QueryRoleOption().then((res: string[]) => {
let temRoleNames = res.map((item) => {
return {
value: item,
}
});
if (!initialState?.currentUser?.roleNames.includes("Super Admin")) {
temRoleNames = res.filter(item => item !== "Super Admin").map((item) => {
return {
value: item,
}
});
}
console.log("temRoleNames", temRoleNames);
setRoleOptions(temRoleNames);
}).catch((error: any) => {
messageApi.error(error.message);
})
};
// 组件加载时获取数据
useEffect(() => {
fetchOptions();
setFormRef(form);
debugger
if (isEdit) {
fetchOptionDetail();
}
}, [optionKey, open, setFormRef]);
return (
<Spin spinning={loading} tip={tip}>
<Card title={isEdit ? '修改选项' : '添加选项'}>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
>
<Form.Item name="key" label="键" rules={[{ required: true, message: '请输入键' }]}>
<Input placeholder="请输入键" allowClear />
</Form.Item>
<Form.Item
name="value"
label="键值"
rules={[{ required: true, message: '请输入键值' }]}
>
<TextArea
placeholder="请输入键值"
autoSize={{ minRows: 4, maxRows: 8 }}
/>
</Form.Item>
<Form.Item name="type" label="值类型" rules={[{ required: true, message: '请选择值类型' }]}>
<Select
placeholder="请选择值类型"
options={getOptionTypeOptions()}
allowClear
/>
</Form.Item>
<Form.Item name="category" label="数据分类" rules={[{ required: true, message: '请选择数据分类' }]}>
<Select
allowClear
placeholder="请选择数据分类"
options={getOptionCategoryOptions()}
/>
</Form.Item>
<Form.Item name="roleNames" label="权限角色" help="选择角色分组后,只有该角色组的用户才能使用该选项">
<Select
allowClear
mode="multiple"
tagRender={tagRender}
options={roleOptions}
placeholder="请选择角色分组"
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
icon={<SaveOutlined />}
style={{ marginRight: 16 }}
>
</Button>
</Form.Item>
</Form>
</Card>
{messageHolder}
</Spin>
);
};
export default AddModifyOption;

View File

@ -0,0 +1,443 @@
import React, { useEffect, useState } from 'react';
import { Table, Form, Input, Button, Space, Select, message, Popconfirm, Tag, SelectProps, Modal } from 'antd';
import { PlusOutlined, SearchOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { cusRequest } from '@/request';
import { useModel } from '@umijs/max';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { history } from 'umi';
import TemplateContainer from '../TemplateContainer';
import { QueryRoleOption } from '@/services/services/role';
import { getOptionCategoryOptions, getOptionTypeOptions, OptionCategory } from '@/services/enum/optionEnum';
import { OptionModel } from '@/services/typing/options/option';
import { CheckJsonString, objectToQueryString } from '@/services/services/common';
import JsonView from '@uiw/react-json-view';
import { useFormReset } from '@/hooks/useFormReset';
import AddModifyOption from './AddModifyOption';
import { useSoftStore } from '@/store/software';
const OptionsManagement: React.FC = () => {
const { initialState } = useModel('@@initialState');
const [form] = Form.useForm();
const [loading, setLoading] = useState<boolean>(false);
const { setTopSpinning, setTopSpinTip } = useSoftStore();
const [key, setKey] = useState<string | undefined>(undefined);
const [category, setCategory] = useState<OptionCategory | undefined>(undefined);
const [dataSource, setDataSource] = useState<OptionModel.OptionsItem[]>([]);
const [openModal, setOpenModal] = useState<boolean>(false);
const [roleOptions, setRoleOptions] = useState<SelectProps['options']>([]);
const [tableParams, setTableParams] = useState<TableModel.TableParams>({
pagination: {
current: 1,
pageSize: 10,
showQuickJumper: true,
totalBoundaryShowSizeChanger: true,
},
});
const [messageApi, messageHolder] = message.useMessage();
const [modalApi, modalHolder] = Modal.useModal();
const { setFormRef, resetForm } = useFormReset();
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
const [currentJsonData, setCurrentJsonData] = useState<any>(null);
const [isJsonFormat, setIsJsonFormat] = useState<boolean>(false);
type TagRender = SelectProps['tagRender'];
// 获取选项列表
const fetchOptions = async () => {
QueryRoleOption().then((res: string[]) => {
let temRoleNames = res.map((item) => {
return {
value: item,
}
});
if (!initialState?.currentUser?.roleNames.includes("Super Admin")) {
temRoleNames = res.filter(item => item !== "Super Admin").map((item) => {
return {
value: item,
}
});
}
console.log("temRoleNames", temRoleNames);
setRoleOptions(temRoleNames);
}).catch((error: any) => {
messageApi.error(error.message);
})
};
// 删除选项
const handleDelete = async (record: OptionModel.OptionsItem) => {
try {
let confirm = await modalApi.confirm({
title: "确认删除",
content: "该操作会删除当前数据信息,请谨慎操作!",
okText: "继续",
});
if (!confirm) {
messageApi.info("已取消删除操作");
return;
};
setTopSpinning(true);
setTopSpinTip("正在删除选项");
const res = await cusRequest<any>(`/lms/Options/DeleteOptionsByKey/${record.category}/${record.key}`, {
method: 'DELETE',
});
if (res.code === 1) {
messageApi.success('删除成功');
await QueryOption();
} else {
messageApi.error(res.message || '删除失败');
}
} catch (error) {
console.error('删除选项失败:', error);
messageApi.error('删除失败');
} finally {
setTopSpinning(false);
}
};
// 编辑选项
const handleEdit = (record: OptionModel.OptionsItem) => {
debugger
setKey(record.key);
setCategory(record.category);
setOpenModal(true);
};
// 添加选项
const handleAdd = () => {
setKey("");
setCategory(undefined);
setOpenModal(true);
};
// Function to handle clicking on data
const handleDataClick = (dataString: string) => {
let isJsonString = CheckJsonString(dataString);
if (isJsonString) {
const jsonData = JSON.parse(dataString);
setCurrentJsonData(jsonData);
setIsJsonFormat(true);
} else {
// 不是JSON格式直接展示原始字符串
setCurrentJsonData(dataString);
setIsJsonFormat(false);
}
setIsModalVisible(true);
};
// 关闭Modal的函数
const handleModalClose = () => {
setIsModalVisible(false);
setCurrentJsonData(null);
};
// 表格列定义
const columns: ColumnsType<OptionModel.OptionsItem> = [
{
title: '键',
dataIndex: 'key',
key: 'key',
width: 150,
fixed: 'left',
ellipsis: true,
},
{
title: '值',
dataIndex: 'value',
key: 'value',
ellipsis: true,
render: (text) => (
<a style={{ cursor: 'pointer', color: 'black' }}>
{text}
</a>
),
onCell: (record) => ({
onClick: () => handleDataClick(record.value),
style: { cursor: 'pointer' }
})
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 120,
render: (text) => {
return getOptionTypeOptions().find((item) => item.value == text)?.label;
}
},
{
title: '分类',
dataIndex: 'category',
key: 'category',
width: 120,
render: (text) => {
return getOptionCategoryOptions().find((item) => item.value == text)?.label;
}
},
{
title: '角色',
dataIndex: 'roleNames',
render: (text, record) => {
let res = record.roleNames.map((item) => {
return <Tag key={item} color="cyan">{item}</Tag>
});
return res;
},
width: '340px',
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 160,
render: (_, record) => (
<Space>
<Button
type='primary'
size='small'
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Button danger size='small' onClick={() => handleDelete(record)} icon={<DeleteOutlined />}>
</Button>
</Space>
),
},
];
async function QueryOption() {
await queryOptionBasic(null);
}
// 查询查询数据
async function queryOptionBasic(pagination: TablePaginationConfig | null) {
setLoading(true);
try {
let tableParamsParams = pagination ? { pagination } : tableParams;
let query = objectToQueryString({
...form.getFieldsValue(),
page: tableParamsParams.pagination?.current ?? 1,
pageSize: tableParamsParams.pagination?.pageSize ?? 10,
});
let res = await cusRequest<BasicModel.QueryCollection<OptionModel.OptionsItem[]>>(`/lms/Options/QueryOptionCollection?${query}`, {
method: 'GET',
});
if (res.code != 1) {
messageApi.error(res.message);
return;
}
if (res.data == undefined) {
messageApi.error("没有查询到数据");
return
}
setDataSource(res.data.collection);
setTableParams({
pagination: {
...tableParamsParams.pagination,
total: res.data.total,
},
});
} catch (error: any) {
messageApi.error(error.message);
} finally {
setLoading(false);
}
}
// 表格变化处理
const handleTableChange = async (
pagination: any,
filters: any,
sorter: any
) => {
setLoading(true);
try {
await queryOptionBasic(pagination);
setTableParams({
pagination: {
...pagination,
}
})
} catch (error: any) {
message.error(error.message);
} finally {
setLoading(false);
}
};
const tagRender: TagRender = (props) => {
const { label, value, closable, onClose } = props;
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault();
event.stopPropagation();
};
return (
<Tag
color="cyan"
onMouseDown={onPreventMouseDown}
closable={closable}
onClose={onClose}
style={{ marginInlineEnd: 4 }}
>
{label}
</Tag>
);
};
async function modalCancel(): Promise<void> {
setOpenModal(false);
setKey('');
setCategory(undefined);
resetForm();
// 这边调用加载数据的方法
await QueryOption();
}
// 初始化加载
useEffect(() => {
fetchOptions();
QueryOption();
}, []);
return (
<TemplateContainer navTheme={initialState?.settings?.navTheme ?? "light"}>
<Form
form={form}
layout="inline"
onFinish={QueryOption}
style={{ marginBottom: 16 }}
>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '16px', marginBottom: '16px' }}>
<Form.Item name="key" label="键">
<Input placeholder="请输入键" style={{ width: 200 }} allowClear />
</Form.Item>
<Form.Item name="type" label="值类型">
<Select
placeholder="请选择值类型"
style={{ width: 200 }}
options={getOptionTypeOptions()}
allowClear
/>
</Form.Item>
<Form.Item name="category" label="数据分类">
<Select
allowClear
placeholder="请选择数据分类"
style={{ width: 200 }}
options={getOptionCategoryOptions()}
/>
</Form.Item>
<Form.Item name="roleNames" label="权限角色">
<Select
allowClear
mode="multiple"
tagRender={tagRender}
style={{ maxWidth: '400px', minWidth: "260px" }}
options={roleOptions}
placeholder="请选择角色分组"
/>
</Form.Item>
<Form.Item>
<Space>
<Button type="primary" htmlType="submit" icon={<SearchOutlined />}>
</Button>
<Button onClick={() => {
form.resetFields()
QueryOption()
}}>
</Button>
<Button type="primary" onClick={handleAdd} icon={<PlusOutlined />}>
</Button>
</Space>
</Form.Item>
</div>
</Form>
<Table
columns={columns}
rowKey="id"
dataSource={dataSource}
pagination={tableParams.pagination}
loading={loading}
onChange={handleTableChange}
scroll={{ x: 1500 }}
/>
{/* JSON 数据查看器 Modal */}
<Modal
title="数据详情"
open={isModalVisible}
onCancel={handleModalClose}
footer={null}
width={800}
>
{isJsonFormat ? (
<JsonView
value={currentJsonData}
displayDataTypes={false}
displayObjectSize={false}
enableClipboard={true}
style={{
padding: '10px',
borderRadius: '4px',
maxHeight: '70vh',
overflow: 'auto',
}}
/>
) : (
<pre
style={{
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
maxHeight: '70vh',
overflow: 'auto',
padding: '10px',
background: '#f5f5f5',
borderRadius: '4px',
}}
>
{currentJsonData}
</pre>
)}
</Modal>
<Modal width={840} maskClosable={false} open={openModal} footer={null} onCancel={modalCancel}>
<AddModifyOption setFormRef={setFormRef} open={openModal} optionKey={key} category={category} />
</Modal>
{messageHolder}
{modalHolder}
</TemplateContainer>
);
};
export default OptionsManagement;

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { Table, Button, message, Space, Spin, Form, Input, Select, Modal } from 'antd'; import { Table, Button, message, Space, Spin, Form, Input, Select, Modal } from 'antd';
import TemplateContainer from '@/pages/TemplateContainer'; import TemplateContainer from '@/pages/TemplateContainer';
import { useModel } from '@umijs/max'; import { useModel } from '@umijs/max';
import { objectToQueryString } from '@/services/services/common'; import { CheckJsonString, objectToQueryString } from '@/services/services/common';
import { GetDataInfoTypeOption, GetDataInfoTypeOptions } from '@/services/enum/dataInfo'; import { GetDataInfoTypeOption, GetDataInfoTypeOptions } from '@/services/enum/dataInfo';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
@ -68,18 +68,11 @@ const DataInfo: React.FC = () => {
const [currentJsonData, setCurrentJsonData] = useState<any>(null); const [currentJsonData, setCurrentJsonData] = useState<any>(null);
const [isJsonFormat, setIsJsonFormat] = useState<boolean>(false); const [isJsonFormat, setIsJsonFormat] = useState<boolean>(false);
function CheckJson(str: string) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
// Function to handle clicking on data // Function to handle clicking on data
const handleDataClick = (dataString: string) => { const handleDataClick = (dataString: string) => {
let isJsonString = CheckJson(dataString); let isJsonString = CheckJsonString(dataString);
if (isJsonString) { if (isJsonString) {
const jsonData = JSON.parse(dataString); const jsonData = JSON.parse(dataString);

View File

@ -5,6 +5,43 @@ export enum OptionType {
Boolean = 4, Boolean = 4,
} }
/**
*
* @returns
*/
export function getOptionTypeOptions(): {
label: string;
value: OptionType;
}[] {
return [
{ label: "String", value: OptionType.String },
{ label: "JSON", value: OptionType.JSON },
{ label: "Number", value: OptionType.Number },
{ label: "Boolean", value: OptionType.Boolean },
];
}
export enum OptionCategory {
System = 1,
LaiTool = 2,
NanFeng = 3,
}
/**
*
* @returns
*/
export function getOptionCategoryOptions(): {
label: string;
value: OptionCategory;
}[] {
return [
{ label: "System", value: OptionCategory.System },
{ label: "LaiTool", value: OptionCategory.LaiTool },
{ label: "NanFengAI", value: OptionCategory.NanFeng },
];
}
export enum AllOptionKeyName { export enum AllOptionKeyName {
/** 获取所有的 Option */ /** 获取所有的 Option */

View File

@ -25,4 +25,56 @@ function objectToQueryString(params: Record<string, any>): string {
export { export {
objectToQueryString objectToQueryString
}
/**
* JSON
* @param str
* @returns JSON则返回truefalse
*/
export function CheckJsonString(str: string): boolean {
// 处理无效输入
if (!str || typeof str !== 'string') {
return false;
}
// 去除空白字符
const trimmed = str.trim();
// 排除简单值
const simpleValues = ['true', 'false', 'null', 'undefined', '1', '0'];
if (simpleValues.includes(trimmed.toLowerCase())) {
return false;
}
// 排除纯数字
if (!isNaN(Number(trimmed)) && isFinite(Number(trimmed))) {
return false;
}
// 尝试解析JSON
try {
const parsed = JSON.parse(trimmed);
// 进一步检查是否为有意义的对象或数组
if (parsed === null) {
return false;
}
if (typeof parsed === 'object') {
// 检查是否为空对象或空数组
if (Array.isArray(parsed)) {
return parsed.length > 0; // 非空数组
} else {
return Object.keys(parsed).length > 0; // 非空对象
}
}
// 其他非对象类型(数字、字符串等)
return false;
} catch (e) {
// 解析失败不是有效的JSON
return false;
}
} }

View File

@ -2,6 +2,8 @@ declare namespace AccessType {
interface AccessType { interface AccessType {
canPrompt: boolean; canPrompt: boolean;
canRoleManagement: boolean; canRoleManagement: boolean;
/** 是不是显示数据管理 */
canOptionManagement : boolean;
//#region 用户权限 //#region 用户权限
/** 是不是显示用户管理的菜单 */ /** 是不是显示用户管理的菜单 */

12
src/services/typing/basic.d.ts vendored Normal file
View File

@ -0,0 +1,12 @@
declare namespace BasicModel {
/**
*
*/
interface QueryCollection<T> {
current: number;
total: number;
collection: T;
}
}

View File

@ -1,9 +1,18 @@
import { OptionCategory } from "@/services/enum/optionEnum";
declare namespace OptionModel { declare namespace OptionModel {
type Option = { interface Option {
key: string; key?: string;
value: string; value: string;
type: OptionType; type: OptionType;
} }
interface OptionsItem extends Option {
roleNames: string[];
category: OptionCategory;
}
} }