浏览代码

fix:
1.SQL创建模型BUG
2.用户管理,角色管理接口绑定

WanRuixiang 9 月之前
父节点
当前提交
01c0b31f04

+ 34 - 22
src/app/hooks/useModelConfig.js

@@ -37,31 +37,44 @@ export function useModelConfig() {
    * }
    * */
   const toFields = (sql) => {
+    const regexf = /from/i;                         // 匹配FROM的规则
+    const regexas = /\s+AS\s+/i;                    // 匹配AS的规则
+    const regexs = /select/gi;                      // 匹配SELECT的规则
+    const regext = /FROM\s+([\w]+)/i;               // 匹配表名的规则
+    const regexc =  /[^,]*\([^)]*\) AS [^,]*/gi;    // 匹配复合字段规则 如 CONVERT(admDate,datetime) as admDate,  CAST(admDate AS DATE) as admDate2,
+    const regexcs = /\)\s+as\s+([\w]+)/i;           // 取admDate/admDate2
+    const regexjoin = /\b(LEFT\s+JOIN|RIGHT\s+JOIN|INNER\s+JOIN)\b/i; // 匹配JOIN的规则
+
     // 取SELECT-FROM之间的字符串,并分割出所有字段
-    const regex1 = /from/i;
-    const match = sql.match(regex1);
-    const fromIndex = match?.index || 0;
-    const fieldStr = sql.slice(0, fromIndex).replace(/select/gi,'');
+    const matchf = sql.match(regexf);
+    const fromIndex = matchf?.index || 0;
+    let matchcStr = sql.slice(0, fromIndex).replace(regexs,'');
+
+    // 匹配COUNT()类型的字段,摘取出来单独处理
+    const matchc = matchcStr.match(regexc);
+    let fieldc = [];
+    if (matchc){
+      matchcStr = matchcStr.replace(regexc, '');
+      fieldc = matchc.map(c=>c.match(regexcs)[1]);
+    }
+
     // 按逗号分割字段
-    const fieldArr = fieldStr.split(',');
+    const fieldArr = matchcStr.split(',');
+    // 截取form之后的字符串
+    const sqlRelation = sql.slice(fromIndex);
     // 判断是否有JOIN
-    const sqlRelation = sql.slice(fromIndex)
-    const regex = /\b(LEFT\s+JOIN|RIGHT\s+JOIN|INNER\s+JOIN)\b/i;
-    const isJoin = regex.test(sqlRelation.toUpperCase());
-
+    const isJoin = regexjoin.test(sqlRelation.toUpperCase());
     // 单表查询直接取表名
-    const regex2 = /FROM\s+([\w_]+)/i;
-    const matchStr = sqlRelation.match(regex2);
+    const matchStr = sqlRelation.match(regext);
     const singleTable = ( matchStr && matchStr[1] ) || "";
 
     // 按顺序处理SQL字符串
-    return fieldArr.map(item=> {
+    return fieldArr.concat(fieldc).map(item=> {
       let str=item,table=singleTable,field,aliasStr;
       // 判断字符串是否包含AS,有则去除AS
-      const asRegex = /\s+AS\s+/i;
-      const hasAs = asRegex.test(item);
+      const hasAs = regexas.test(item);
       if (hasAs){
-        const asStr = item.match(asRegex)[0];
+        const asStr = item.match(regexas)[0];
         str = item.split(asStr)[0];
         aliasStr = item.split(asStr)[1].trim();
       }
@@ -101,13 +114,11 @@ export function useModelConfig() {
     const match = sql.match(regex1);
     const fromIndex = match?.index || 0;
     const sqlRelation = sql.slice(fromIndex);
-    let SQLFields = "SELECT "
+    let SQLFields = "SELECT \r\n"
     fields.forEach(({bizName,tableName,fieldName,dataType,comment},index) => {
-      if (index===fields.length-1){
-        SQLFields += `${tableName}.${fieldName} \r\n`
-      }else {
-        SQLFields += `${tableName}.${fieldName},\r\n`
-      }
+      // 是否最后一个
+      const isLast = index===fields.length-1
+      SQLFields += `\t${tableName}.${fieldName}${!isLast&&","}\r\n`
       const initObj = bizs.find(item=>item.name===fieldName);
       tableData.push({
         id:Math.random(),
@@ -141,6 +152,7 @@ export function useModelConfig() {
   const sqlResultToTable = (model,columns,fields,init,bizs) => {
     try {
       let tableData = [];
+      console.log(bizs)
       columns.forEach(col=>{
         // SQL中摘出的字段、别名、表
         let table="";
@@ -187,7 +199,7 @@ export function useModelConfig() {
             isKey:DIM_TYPE_ENUM.IDENTIFY===initObj?.type,
             common:DIM_TYPE_ENUM.CATEGORICAL===initObj?.type,
             datetime:DIM_TYPE_ENUM.DATETIME===initObj?.type,
-            measure:!initObj?.type
+            measure:bizs.length>0 && !initObj?.type
           }
         )
       })

+ 4 - 16
src/app/pages/LoginPage/LoginForm.jsx

@@ -6,24 +6,22 @@ import {
   ProFormText,
 } from '@ant-design/pro-components';
 import {styled} from 'styled-components';
-import {Button, Tabs, message, theme} from 'antd';
+import {Button, Tabs, message} from 'antd';
 import { useState} from 'react';
 import {STATIC_RESOURCE, AUTH_CLIENT_ICON_MAPPING} from './constants.jsx';
 import useI18NPrefix from '../../hooks/useI18NPrefix.js';
-import CryptoJS from "crypto-js";
-const secretKey = import.meta.env.VITE_SECRET_KEY;
+import {encryptPassword} from "../../../utils/auth";
 
 export function LoginForm({onLogin}) {
   const [loginType, setLoginType] = useState('account');
 
   const t = useI18NPrefix('login');
   const g = useI18NPrefix('global');
-  const {token} = theme.useToken();
-  console.log(token)
   const containerStyle = {
     backgroundColor: 'rgba(255,255,255,0.25)',
     backdropFilter: 'blur(4px)',
   }
+
   /** 左下角的活动面板 */
   const activityConfig = {
     boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.2)',
@@ -32,17 +30,6 @@ export function LoginForm({onLogin}) {
     backdropFilter: 'blur(4px)',
   }
 
-  const encryptPassword =(password)=>{
-    if (!password) {
-      return password;
-    }
-    // TODO This key should be stored in a secure place
-    const key = CryptoJS.enc.Utf8.parse(secretKey);
-    const srcs = CryptoJS.enc.Utf8.parse(password);
-    const encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7});
-    return encrypted.toString();
-  }
-
   const onFinish=(values)=>{
     const {userName,password}=values
     onLogin({
@@ -50,6 +37,7 @@ export function LoginForm({onLogin}) {
       password:encryptPassword(password)
     })
   }
+
   return (
     <ProConfigProvider hashed={false}>
       <LoginContent>

+ 25 - 0
src/app/pages/MainPage/slice/thunks.js

@@ -16,6 +16,19 @@ export const getUsers = createAsyncThunk(
   },
 );
 
+/** 创建用户 */
+export const updateUser = createAsyncThunk(
+  'main/updateUser',
+  async ({ params }) => {
+    const { data } = await request({
+      url: "/auth/user/update",
+      data: params,
+      method: "POST",
+    })
+    return data;
+  },
+);
+
 /** 创建用户 */
 export const createUser = createAsyncThunk(
   'main/createUser',
@@ -38,6 +51,18 @@ export const getRoles = createAsyncThunk(
   },
 );
 
+/** 删除角色 */
+export const deleteRoles = createAsyncThunk(
+  'main/deleteRoles',
+  async (id) => {
+    const { data } = await request({
+      url:'/semantic/role/'+id,
+      method: "DELETE",
+    })
+    return data;
+  },
+);
+
 /** 创建用户 */
 export const saveOrUpdateRole = createAsyncThunk(
   'main/createRole',

+ 5 - 7
src/app/pages/ModalManage/components/ModalDesign.jsx

@@ -15,6 +15,8 @@ import useI18NPrefix from "../../../hooks/useI18NPrefix";
 import {selectDimensions} from "../../MainPage/slice/selectors";
 import {useModelConfig} from "../../../hooks/useModelConfig";
 
+// TODO SQL执行解析优化
+
 export const ModalDesign = memo(({
   open=false,
   onCancel,
@@ -184,26 +186,22 @@ export const ModalDesign = memo(({
       if (initialValues && !sqlFields){
         const SQL = modelDetail?.sqlQuery || "";
         const initInfo = initSqlModel(model,SQL,initFields,bizList)
-        sqlRef.current=initInfo.sql;
+        sqlRef.current=SQL;
         setSqlStatus(true);
         return initInfo.tableData
       }
-
       if(sqlFields){
         // 判断SELECT * 时,取接口里的字段进行填充
-        // TODO 单表截取表名,多表想办法
+        // TODO 多表截取字段处理
         const isSelectAll = checkSelectAll(sqlRef.current);
         if (isSelectAll){
           // 单表SELECT *
           const fieldFormat = toFields(sqlRef.current);
           const tableName = fieldFormat.length>0 && fieldFormat[0]?.table;
-          console.log(fieldFormat)
-          console.log(tableName)
-          return fieldsToTable(tableName,sqlFields)
+          return fieldsToTable(tableName,sqlFields);
         }else {
           // SQL字符串转字段数组
           const fieldFormat = toFields(sqlRef.current);
-          console.log(fieldFormat)
           const tablesInfo = sqlResultToTable(model,sqlFields,fieldFormat,initFields,bizList);
           if (tablesInfo){
             return tablesInfo

+ 6 - 3
src/app/pages/RoleManage/index.jsx

@@ -8,7 +8,7 @@ import {selectTouring, selectTourName} from '../../slice/selectors.js'
 import {columnsFactory} from '../../../utils/columnsFactory.jsx'
 import {ProTableDefault, STATUS_TYPE} from '../../constants.js'
 import {selectRoles} from "../MainPage/slice/selectors";
-import {getRoles, saveOrUpdateRole} from "../MainPage/slice/thunks";
+import {deleteRoles, getRoles, saveOrUpdateRole} from "../MainPage/slice/thunks";
 import {appActions} from "../../slice";
 import useI18NPrefix from "../../hooks/useI18NPrefix";
 
@@ -65,7 +65,7 @@ export const RoleManagePage = () => {
         saveOrUpdateRole({
           params,
           resolve: () => {
-            dispatch(getRoles({params}))
+            actionRef.current?.reload()
           },
         })
       )
@@ -74,7 +74,10 @@ export const RoleManagePage = () => {
 
   // 删除行数据
   const onDelete = useCallback(async (rowKey, data) => {
-    console.log(data)
+    const {payload} = await dispatch(deleteRoles(data?.id))
+    if (payload){
+      actionRef.current?.reload()
+    }
   },[])
 
   return (

+ 38 - 23
src/app/pages/UserManage/index.jsx

@@ -1,6 +1,6 @@
 import {PlusOutlined} from '@ant-design/icons';
 import {ProTable} from '@ant-design/pro-components';
-import {Button, Tour} from 'antd';
+import {Button, message, Tour} from 'antd';
 import {useCallback, useEffect, useMemo, useRef} from 'react';
 import {useLocation} from 'react-router-dom';
 import {useAppDispatch, useAppSelector} from '../../../redux/configureStore.js'
@@ -8,9 +8,10 @@ import {selectTouring, selectTourName} from '../../slice/selectors.js'
 import {columnsFactory, optionFactory} from '../../../utils/columnsFactory.jsx'
 import {ProTableDefault} from '../../constants.js'
 import {selectRoles, selectUsers} from "../MainPage/slice/selectors";
-import {createUser, getRoles, getUsers} from "../MainPage/slice/thunks";
+import {createUser, getRoles, getUsers, updateUser} from "../MainPage/slice/thunks";
 import {appActions} from "../../slice";
 import useI18NPrefix from "../../hooks/useI18NPrefix";
+import {encryptPassword} from "../../../utils/auth";
 
 export const UserManagePage = () => {
   const t = useI18NPrefix('global');
@@ -24,7 +25,6 @@ export const UserManagePage = () => {
 
   useEffect(() => {
     dispatch(getRoles({params:{}}))
-    // getColumns()
   }, [])
 
   /** 导游逻辑 */
@@ -55,38 +55,56 @@ export const UserManagePage = () => {
           dataIndex: 'userName',
         },
         {
-          title: t("columns.superAdmin"),
-          dataIndex: 'superAdmin',
-        },
-        {
-          title: t("columns.email"),
-          dataIndex: 'email',
+          title: t("columns.displayName"),
+          dataIndex: 'displayName',
         },
         {
           title:  t("columns.role"),
-          dataIndex: 'roleName',
+          dataIndex: 'roleId',
           valueType: 'select',
           fieldProps: {
             options: optionFactory(roleList)
           }
         },
         {
-          title: t("columns.admin"),
-          dataIndex: 'isAdmin',
+          title: t("columns.mobile"),
+          dataIndex: 'mobile',
+        },
+        {
+          title: t("columns.password"),
+          dataIndex: 'password',
+          valueType: 'password',
         }
       ]
     })
   ,[roleList])
 
   // 保存行数据
-  const onSave = useCallback(async (rowKey, params, row) => {
-    const newObj = JSON.stringify(params)
-    const oldObj = JSON.stringify(row)
-    if (newObj !== oldObj) {
+  const onSave = useCallback(async (rowKey, value) => {
+    let params = Object.assign({}, value);
+
+    params.password = encryptPassword(value.password);
+
+    if (params?.id){
+      await dispatch(updateUser({ params }))
+    }else {
       await dispatch(createUser({ params }))
     }
+
+    actionRef.current?.reload()
   },[])
 
+  // 删除用户
+  const onDelete = useCallback(async ()=>{
+    message.warning("此功能暂未开启")
+  },[])
+
+  const onAddRow = ()=>{
+    actionRef.current?.addEditRecord?.({
+      key: (Math.random() * 1000000).toFixed(0)
+    }, {position: "top"});
+  }
+
   return (
     <>
       <Tour open={tourName === pathname && touring} onClose={closeTouring} steps={steps}/>
@@ -103,7 +121,8 @@ export const UserManagePage = () => {
           dataSource={userList}
           editable={{
             type: 'multiple',
-            onSave
+            onSave,
+            onDelete
           }}
           pagination={{
             pageSize: 5,
@@ -114,14 +133,10 @@ export const UserManagePage = () => {
               ref={ref2}
               key="button"
               icon={<PlusOutlined/>}
-              onClick={() => {
-                actionRef.current?.addEditRecord?.({
-                  key: (Math.random() * 1000000).toFixed(0)
-                }, {position: "top"});
-              }}
+              onClick={onAddRow}
               type="primary"
             >
-              {t("button.save")}
+              {t("button.create")}
             </Button>
           ]}
         />

+ 4 - 1
src/locales/en/translation.json

@@ -353,7 +353,10 @@
       "superAdmin": "Super Admin",
       "email": "Email",
       "roleName": "Role",
-      "admin": "Admin"
+      "admin": "Admin",
+      "displayName": "Display Name",
+      "password": "Password",
+      "mobile": "Mobile"
     },
     "component": {
       "collected": "Collected",

+ 4 - 1
src/locales/ko/translation.json

@@ -353,7 +353,10 @@
       "superAdmin": "슈퍼 관리자",
       "email": "이메일 주소",
       "roleName": "역할 이름",
-      "admin": "관리자"
+      "admin": "관리자",
+      "displayName": "이름",
+      "password": "암호",
+      "mobile": "전화 번호"
     },
     "component": {
       "collected": "수집되었습니다",

+ 4 - 1
src/locales/zh/translation.json

@@ -353,7 +353,10 @@
       "superAdmin": "超级管理员",
       "email": "邮箱地址",
       "roleName": "角色名称",
-      "admin": "管理员"
+      "admin": "管理员",
+      "displayName": "中文名",
+      "password": "密码",
+      "mobile": "手机号"
     },
     "component": {
       "collected": "已收藏",

+ 13 - 1
src/utils/auth.js

@@ -1,8 +1,9 @@
 import {
-  BASE_API_URL,
   DEFAULT_AUTHORIZATION_TOKEN_EXPIRATION,
   StorageKeys,
 } from '../globalConstants';
+import CryptoJS from "crypto-js";
+const secretKey = import.meta.env.VITE_SECRET_KEY;
 
 let tokenExpiration = DEFAULT_AUTHORIZATION_TOKEN_EXPIRATION;
 
@@ -25,3 +26,14 @@ export function setToken(token) {
 export function removeToken() {
   localStorage.removeItem(StorageKeys.AuthorizationToken);
 }
+
+export function encryptPassword(password){
+  if (!password) {
+    return password;
+  }
+  const key = CryptoJS.enc.Utf8.parse(secretKey);
+  const srcs = CryptoJS.enc.Utf8.parse(password);
+  const encrypted = CryptoJS.AES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7});
+  return encrypted.toString();
+}
+

+ 1 - 1
vite.config.js

@@ -65,7 +65,7 @@ export default defineConfig(({mode})=> {
         // 当有 /api开头的地址是,代理到target地址
         '^/api': {
           target: 'http://172.20.2.23:18084/', //目标源,目标服务器,真实请求地址
-          //target: 'http://192.168.2.97:9080/', //目标源,目标服务器,真实请求地址
+          //target: 'http://192.168.2.211:9080/', //目标源,目标服务器,真实请求地址
           changeOrigin: true, //支持跨域
           rewrite: (path) => path.replace(/^\/api/, "/api"), //重写真实路径,替换/api
         }