Bläddra i källkod

Merge remote-tracking branch 'origin/dev' into dev

LT32820A 2 månader sedan
förälder
incheckning
f3ee08b3a9

+ 14 - 0
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/controller/AppmanageController.java

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.jeecg.common.api.vo.Result;
+import org.jeecg.common.aspect.annotation.PermissionData;
 import org.jeecg.modules.system.entity.AppmanageEntity.*;
 import org.jeecg.modules.system.service.AppmanageService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -167,6 +168,19 @@ public class AppmanageController {
         return Result.ok(appInfoService.isAppAdminOrDevOrOpe(appid));
     }
 
+
+    // 获取应用列表(权限隔离)
+    @PermissionData
+//    @RequiresPermissions("sys:app:admin")
+    @RequestMapping(value = "/appList", method = RequestMethod.GET)
+    public Result<List<AppBaseInfo>> adminAppList() {
+        List<AppBaseInfo> appBaseInfos = appInfoService.appList();
+        Result<List<AppBaseInfo>> result = new Result<>();
+        result.setResult(appBaseInfos);
+        result.setSuccess(true);
+        return result;
+    }
+
     // 应用注册审核通过后续处理(微服务调用)
     @GetMapping("/afterAppCheckPass")
     public void afterAppCheckPass(@RequestParam("appid")String appid){

+ 3 - 0
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/mapper/AppBaseInfoMapper.java

@@ -28,4 +28,7 @@ public interface AppBaseInfoMapper extends BaseMapper<AppBaseInfo> {
     // 从用户表找到用户账号
     @Select("SELECT username from sys_user where id = #{id}")
     String queryUsernameById(@Param("id") String id);
+
+    //应用列表(数据隔离)
+    List<AppBaseInfo> appList(@Param("permissionSql") String permissionSql);
 }

+ 3 - 0
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/AppmanageService.java

@@ -60,6 +60,9 @@ public interface AppmanageService  {
     // 当前登录用户是否拥有平台应用管理员
     boolean isAppAdmin();
 
+    // 应用列表(数据隔离)
+    List<AppBaseInfo> appList();
+
     // 是否为应用管理员或开发负责人、运维负责人
     boolean isAppAdminOrDevOrOpe(String appid);
 

+ 25 - 0
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/system/service/impl/AppmanageServiceImpl.java

@@ -9,8 +9,10 @@ import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.dto.message.TemplateMessageDTO;
 import org.jeecg.common.config.mqtoken.UserTokenContext;
 import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.system.query.QueryGenerator;
 import org.jeecg.common.system.vo.LoginUser;
 import org.jeecg.common.util.RedisUtil;
+import org.jeecg.common.util.oConvertUtils;
 import org.jeecg.modules.base.service.BaseCommonService;
 import org.jeecg.modules.client.AppManage.WorkFlowClient;
 import org.jeecg.modules.system.controller.SysUserController;
@@ -651,6 +653,29 @@ public class AppmanageServiceImpl implements AppmanageService {
         return loginUser.getId().equals(baseInfo.getAdmin());
     }
 
+    @Override
+    public List<AppBaseInfo> appList() {
+        String sql = QueryGenerator.installAuthJdbc(AppBaseInfo.class);
+        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        Boolean isAdmin = loginUser.getUsername().equals("admin");
+        if (isAdmin) return baseInfoMapper.appList("");
+        if (oConvertUtils.isNotEmpty(sql)){
+            //对sql进行处理避免注入
+            if (sql.startsWith(" and id in (")&& sql.endsWith(")")){
+                StringBuilder inSql = new StringBuilder("AND id IN (");
+                String inClause = sql.substring(" and id in (".length(), sql.length() - 1);
+                List<String> ids = Arrays.asList(inClause.split(","));
+                if (!ids.isEmpty()){
+                    ids.forEach(id -> inSql.append(id).append(","));
+                    inSql.deleteCharAt(inSql.length() - 1);
+                }else inSql.append(inClause);
+                inSql .append(") ");
+                return baseInfoMapper.appList(inSql.toString());
+            }
+        }
+        return new ArrayList<>();
+    }
+
 
     // 应用审核通过后一系列创建应用一级菜单、默认角色、授权操作(完整注册时)
     public void afterAppCheckPass(String appid) {

+ 143 - 125
jeecgboot-vue3/src/views/system/menu/menu/MenuDrawer.vue

@@ -1,148 +1,166 @@
 <template>
   <BasicDrawer v-bind="$attrs" @register="registerDrawer" showFooter :width="adaptiveWidth" :title="getTitle" @ok="handleSubmit">
-    <BasicForm @register="registerForm" class="menuForm" />
+    <BasicForm @register="registerForm" class="menuForm">
+      <template #appId="{ model, field }">
+        <!-- 如果是组件需要进行双向绑定,model当前表单对象,field当前字段名称  -->
+        <a-select v-model:value="model[field]" :options="appList" placeholder="请选择应用" :disabled="isUpdate" @change="AppChange" />
+      </template>
+    </BasicForm>
   </BasicDrawer>
 </template>
 <script lang="ts" setup>
-  import { ref, computed, unref, useAttrs } from 'vue';
-  import { BasicForm, useForm } from '/@/components/Form/index';
-  import { formSchema, ComponentTypes } from './menu.data';
-  import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
-  import { list, saveOrUpdateMenu } from './menu.api';
-  import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
-  import { useI18n } from "/@/hooks/web/useI18n";
-  // 声明Emits
-  const emit = defineEmits(['success', 'register']);
-  const { adaptiveWidth } = useDrawerAdaptiveWidth();
-  const attrs = useAttrs();
-  const isUpdate = ref(true);
-  const menuType = ref(0);
-  const isButton = (type) => type === 2;
-  const [registerForm, { setProps, resetFields, setFieldsValue, updateSchema, validate, clearValidate }] = useForm({
-    labelCol: {
-      md: { span: 4 },
-      sm: { span: 6 },
+import { ref, computed, unref, useAttrs } from 'vue';
+import { BasicForm, useForm } from '/@/components/Form/index';
+import { formSchema, ComponentTypes } from './menu.data';
+import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
+import { list, saveOrUpdateMenu } from './menu.api';
+import { useDrawerAdaptiveWidth } from '/@/hooks/jeecg/useAdaptiveWidth';
+import { useI18n } from '/@/hooks/web/useI18n';
+// 声明Emits
+const emit = defineEmits(['success', 'register']);
+const { adaptiveWidth } = useDrawerAdaptiveWidth();
+const attrs = useAttrs();
+const isUpdate = ref(true);
+const menuType = ref(0);
+const appList = ref([]);
+const isButton = (type) => type === 2;
+const [registerForm, { setProps, resetFields, setFieldsValue, updateSchema, validate, clearValidate }] = useForm({
+  labelCol: {
+    md: { span: 4 },
+    sm: { span: 6 },
+  },
+  wrapperCol: {
+    md: { span: 20 },
+    sm: { span: 18 },
+  },
+  schemas: formSchema,
+  showActionButtonGroup: false,
+});
+
+const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
+  await resetFields();
+  const type = !!data?.isUpdate;
+  setDrawerProps({ confirmLoading: false, maskClosable: type ? false : true });
+  isUpdate.value = type;
+  menuType.value = data?.record?.menuType;
+  const apps = data?.record?.appList ?? [];
+  appList.value = apps.map((item) => ({ value: item.id, label: item.name }));
+
+  let appId = data?.record?.appId;
+
+  //获取下拉树信息
+  const treeData = await list({ appId: appId });
+  updateSchema([
+   
+    {
+      field: 'parentId',
+      // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
+      componentProps: { treeData: translateMenu(treeData, 'name') },
+      // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
     },
-    wrapperCol: {
-      md: { span: 20 },
-      sm: { span: 18 },
+    {
+      field: 'name',
+      label: isButton(unref(menuType)) ? '按钮/权限' : '菜单名称',
     },
-    schemas: formSchema,
-    showActionButtonGroup: false,
-  });
-
-  const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
-    await resetFields();
-    const type = !!data?.isUpdate;
-    setDrawerProps({ confirmLoading: false ,maskClosable: type?false:true,});
-    isUpdate.value = type;
-    menuType.value = data?.record?.menuType;
+    {
+      field: 'url',
+      required: !isButton(unref(menuType)),
+      componentProps: {
+        onChange: (e) => onUrlChange(e.target.value),
+      },
+    },
+  ]);
 
-    let appId = data?.record?.appId;
+  // 无论新增还是编辑,都可以设置表单值
+  if (typeof data.record === 'object') {
+    let values = { ...data.record };
+    setFieldsValue(values);
+    onUrlChange(values.url);
+  }
+  //按钮类型情况下,编辑时候清除一下地址的校验
+  if (menuType.value == 2) {
+    clearValidate();
+  }
+  //禁用表单
+  setProps({ disabled: !attrs.showFooter });
+});
+//获取弹窗标题
+const getTitle = computed(() => (!unref(isUpdate) ? '新增菜单' : '编辑菜单'));
+//提交事件
+async function handleSubmit() {
+  try {
+    const values = await validate();
+    // iframe兼容
+    if (ComponentTypes.IFrame === values.component) {
+      values.component = values.frameSrc;
+    }
+    setDrawerProps({ confirmLoading: true });
+    //提交表单
+    await saveOrUpdateMenu(values, unref(isUpdate));
+    closeDrawer();
+    emit('success');
+  } finally {
+    setDrawerProps({ confirmLoading: false });
+  }
+}
 
-    //获取下拉树信息
-    const treeData = await list({appId:appId});
+//填充下拉树信息
+function AppChange(value) {
+  list({ appId: value }).then((res) => {
+    setFieldsValue({ parentId: '' });
     updateSchema([
-      {
-        field: 'appId',
-        componentProps: { disabled: type, },
-      },
       {
         field: 'parentId',
-        // update-begin--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
-        componentProps: { treeData: translateMenu(treeData, 'name') },
-        // update-end--author:liaozhiyang---date:20240306---for:【QQYUN-8379】菜单管理页菜单国际化
-      },
-      {
-        field: 'name',
-        label: isButton(unref(menuType)) ? '按钮/权限' : '菜单名称',
-      },
-      {
-        field: 'url',
-        required: !isButton(unref(menuType)),
-        componentProps: {
-          onChange: (e) => onUrlChange(e.target.value),
-        },
+        componentProps: { treeData: translateMenu(res,'name') },
       },
     ]);
-
-    // 无论新增还是编辑,都可以设置表单值
-    if (typeof data.record === 'object') {
-      let values = { ...data.record };
-      setFieldsValue(values);
-      onUrlChange(values.url);
-    }
-    //按钮类型情况下,编辑时候清除一下地址的校验
-    if (menuType.value == 2) {
-      clearValidate();
-    }
-    //禁用表单
-    setProps({ disabled: !attrs.showFooter });
   });
-  //获取弹窗标题
-  const getTitle = computed(() => (!unref(isUpdate) ? '新增菜单' : '编辑菜单'));
-  //提交事件
-  async function handleSubmit() {
-    try {
-      const values = await validate();
-      // iframe兼容
-      if (ComponentTypes.IFrame === values.component) {
-        values.component = values.frameSrc;
-      }
-      setDrawerProps({ confirmLoading: true });
-      //提交表单
-      await saveOrUpdateMenu(values, unref(isUpdate));
-      closeDrawer();
-      emit('success');
-    } finally {
-      setDrawerProps({ confirmLoading: false });
-    }
-  }
+}
 
-  /** url 变化时,动态设置组件名称placeholder */
-  function onUrlChange(url) {
-    let placeholder = '';
-    let httpUrl = url;
-    if (url != null && url != '') {
-      if (url.startsWith('/')) {
-        url = url.substring(1);
-      }
-      url = url.replaceAll('/', '-');
-      // 特殊标记
-      url = url.replaceAll(':', '@');
-      placeholder = `${url}`;
-    } else {
-      placeholder = '请输入组件名称';
+/** url 变化时,动态设置组件名称placeholder */
+function onUrlChange(url) {
+  let placeholder = '';
+  let httpUrl = url;
+  if (url != null && url != '') {
+    if (url.startsWith('/')) {
+      url = url.substring(1);
     }
-    updateSchema([{ field: 'componentName', componentProps: { placeholder } }]);
-    //update-begin---author:wangshuai ---date:20230204  for:[QQYUN-4058]菜单添加智能化处理------------
-    if (httpUrl != null && httpUrl != '') {
-      if (httpUrl.startsWith('http://') || httpUrl.startsWith('https://')) {
-        setFieldsValue({ component: httpUrl });
-      }
+    url = url.replaceAll('/', '-');
+    // 特殊标记
+    url = url.replaceAll(':', '@');
+    placeholder = `${url}`;
+  } else {
+    placeholder = '请输入组件名称';
+  }
+  updateSchema([{ field: 'componentName', componentProps: { placeholder } }]);
+  //update-begin---author:wangshuai ---date:20230204  for:[QQYUN-4058]菜单添加智能化处理------------
+  if (httpUrl != null && httpUrl != '') {
+    if (httpUrl.startsWith('http://') || httpUrl.startsWith('https://')) {
+      setFieldsValue({ component: httpUrl });
     }
-    //update-end---author:wangshuai ---date:20230204  for:[QQYUN-4058]菜单添加智能化处理------------
   }
+  //update-end---author:wangshuai ---date:20230204  for:[QQYUN-4058]菜单添加智能化处理------------
+}
 
-  /**
-  * 2024-03-06
-  * liaozhiyang
-  * 翻译菜单名称
-  */
-  function translateMenu(data, key) {
-    if (data?.length) {
-      const { t } = useI18n();
-      data.forEach((item) => {
-        if (item[key]) {
-          if (item[key].includes("t('") && t) {
-            item[key] = new Function('t', `return ${item[key]}`)(t);
-          }
-        }
-        if (item.children?.length) {
-          translateMenu(item.children, key);
+/**
+ * 2024-03-06
+ * liaozhiyang
+ * 翻译菜单名称
+ */
+function translateMenu(data, key) {
+  if (data?.length) {
+    const { t } = useI18n();
+    data.forEach((item) => {
+      if (item[key]) {
+        if (item[key].includes("t('") && t) {
+          item[key] = new Function('t', `return ${item[key]}`)(t);
         }
-      });
-    }
-    return data;
+      }
+      if (item.children?.length) {
+        translateMenu(item.children, key);
+      }
+    });
   }
+  return data;
+}
 </script>

+ 5 - 3
jeecgboot-vue3/src/views/system/menu/menu/index.vue

@@ -119,7 +119,7 @@ const { prefixCls, tableContext } = useListPage({
       schemas: searchFormSchema,
       autoAdvancedCol: 4,
       baseColProps: { xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
-      actionColOptions: { push:1,xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
+      actionColOptions: { push: 1, xs: 24, sm: 12, md: 6, lg: 6, xl: 6, xxl: 6 },
     },
     actionColumn: {
       width: 120,
@@ -151,9 +151,11 @@ function onSelectChange(selectedRowKeys: (string | number)[]) {
  */
 function handleCreate() {
   showFooter.value = true;
-  let { getFieldsValue } = getForm();
+  let appid = appList.value.length > 0 ? appList.value[0].id : '';
+  console.log('appid', appid);
+  console.log('appList', appList.value);
   openDrawer(true, {
-    record: { appId: getFieldsValue().appId ? getFieldsValue().appId : '0' },
+    record: { appId: appid, appList: appList.value },
     isUpdate: false,
   });
 }

+ 4 - 13
jeecgboot-vue3/src/views/system/menu/menu/menu.data.ts

@@ -3,8 +3,9 @@ import { FormSchema } from '/@/components/Table';
 import { h } from 'vue';
 import { Icon } from '/@/components/Icon';
 
-import { ajaxGetDictItems, checkPermDuplication,appList as getAppList } from './menu.api';
+import { ajaxGetDictItems, checkPermDuplication, appList as getAppList, list } from './menu.api';
 import { render } from '/@/utils/common/renderUtils';
+import { treeData } from '/@/views/demo/tree/data';
 
 const isDir = (type) => type === 0;
 const isMenu = (type) => type === 1;
@@ -75,13 +76,11 @@ export const searchFormSchema: FormSchema[] = [
       valueField: 'id',
       placeholder: '请选择应用',
     },
-    
   },
   {
     field: 'name',
     label: '菜单名称',
     component: 'Input',
-    
   },
 ];
 
@@ -95,15 +94,9 @@ export const formSchema: FormSchema[] = [
   {
     label: '应用',
     field: 'appId',
-    component: 'ApiSelect',
-    componentProps: {
-      api: getAppList,
-      labelField: 'name',
-      valueField: 'id',
-      placeholder: '请选择应用',
-    },
+    component: 'Select',
+    slot: 'appId',
   },
-
   {
     field: 'menuType',
     label: '菜单类型',
@@ -152,13 +145,11 @@ export const formSchema: FormSchema[] = [
     component: 'TreeSelect',
     required: true,
     componentProps: {
-      //update-begin---author:wangshuai ---date:20230829  for:replaceFields已过期,使用fieldNames代替------------
       fieldNames: {
         label: 'name',
         key: 'id',
         value: 'id',
       },
-      //update-end---author:wangshuai ---date:20230829  for:replaceFields已过期,使用fieldNames代替------------
       dropdownStyle: {
         maxHeight: '50vh',
       },